npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aiquants/virtualscroll

v1.15.0

Published

High-performance virtual scrolling component for React with variable item heights

Readme

@aiquants/virtualscroll

High-performance virtual scrolling component for React with variable item heights using Fenwick Tree optimization.

Features

  • High Performance: Optimized for thousands of items with O(log n) operations
  • 📐 Variable Heights: Support for items with different heights
  • 🎯 Precise Scrolling: Accurate scroll positioning and smooth navigation
  • 📱 Touch Support: Full support for touch devices
  • 🎨 Customizable: Flexible styling and theming options
  • 🌀 Ultrafast Tap Scroll: Adaptive tap scroll circle that scales speed up to 120× for massive datasets
  • 🔧 TypeScript: Full TypeScript support with comprehensive type definitions

Installation

npm install @aiquants/virtualscroll
# or
yarn add @aiquants/virtualscroll
# or
pnpm add @aiquants/virtualscroll

Basic Usage

import { VirtualScroll } from '@aiquants/virtualscroll'
import { useCallback } from 'react'

type Item = {
  id: number
  text: string
  height: number
}

const items: Item[] = Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  text: `Item ${i}`,
  height: Math.floor(Math.random() * 50) + 30, // Random height between 30-80px
}))

function App() {
  const getItem = useCallback((index: number) => items[index], [])
  const getItemHeight = useCallback((index: number) => items[index].height, [])

  return (
    <div style={{ height: '400px', width: '100%' }}>
      <VirtualScroll
        itemCount={items.length}
        getItem={getItem}
        getItemHeight={getItemHeight}
        viewportSize={400}
        overscanCount={5}
        className="border border-gray-300"
      >
        {(item, index) => (
          <div
            key={item.id}
            style={{
              height: item.height,
              padding: '8px',
              borderBottom: '1px solid #eee',
              display: 'flex',
              alignItems: 'center',
            }}
          >
            <span>#{index}: {item.text}</span>
          </div>
        )}
      </VirtualScroll>
    </div>
  )
}

API Reference

VirtualScroll Props

| Prop | Type | Required | Description | | ------ | ------ | ---------- | ------------- | | children | (item: T, index: number) => ReactNode | ✅ | Render function for items | | itemCount | number | ✅ | Total number of items | | getItem | (index: number) => T | ✅ | Function to get item at index | | getItemHeight | (index: number) => number | ✅ | Function to get item height | | viewportSize | number | ✅ | Height of the visible area | | overscanCount | number | ❌ | Number of items to render outside viewport (default: 5) | | className | string | ❌ | CSS class name | | onScroll | (position: number, totalHeight: number) => void | ❌ | Scroll event handler | | onRangeChange | (range: VirtualScrollRange) => void | ❌ | Range change handler | | background | ReactNode | ❌ | Background element | | initialScrollIndex | number | ❌ | Initial scroll index | | initialScrollOffset | number | ❌ | Initial scroll offset | | contentInsets | ScrollPaneContentInsets | ❌ | Insets for the content area | | callbackThrottleMs | number | ❌ | Throttle time for scroll callbacks (default: 5ms) | | onItemFocus | (index: number) => void | ❌ | Callback when an item is focused | | scrollBarOptions | VirtualScrollScrollBarOptions | ❌ | Options for the scrollbar | | behaviorOptions | VirtualScrollBehaviorOptions | ❌ | Options for scrolling behavior |

VirtualScrollScrollBarOptions

| Property | Type | Description | | ---------- | ------ | ------------- | | width | number | Width of the scrollbar | | enableThumbDrag | boolean | Enable dragging the scrollbar thumb | | enableTrackClick | boolean | Enable clicking the scrollbar track | | enableArrowButtons | boolean | Enable arrow buttons on the scrollbar | | enableScrollToTopBottomButtons | boolean | Enable "Scroll to Top" and "Scroll to Bottom" buttons | | renderThumbOverlay | (props: ScrollBarThumbOverlayRenderProps) => ReactNode | Render prop to anchor custom UI near the scrollbar thumb | | tapScrollCircleOptions | ScrollBarTapCircleOptions | Customization for the auxiliary tap scroll circle |

VirtualScrollBehaviorOptions

| Property | Type | Description | | ---------- | ------ | ------------- | | enablePointerDrag | boolean | Enable dragging the content area to scroll | | enableKeyboardNavigation | boolean | Enable keyboard navigation (default: true) | | wheelSpeedMultiplier | number | Multiplier for mouse wheel scrolling speed | | inertiaOptions | ScrollPaneInertiaOptions | Physics tuning for drag inertia | | clipItemHeight | boolean | Whether to clip item height (default: false) | | resetOnGetItemHeightChange | boolean | Whether to reset internal height cache when getItemHeight changes (default: false) |

VirtualScrollHandle Methods

| Method | Type | Description | | -------- | ------ | ------------- | | scrollTo | (position: number) => void | Scroll to specific position | | scrollToIndex | (index: number, options?: { align?: "top" \| "bottom" \| "center"; offset?: number }) => void | Scroll to specific item index with optional alignment and offset | | getScrollPosition | () => number | Get current scroll position | | getContentSize | () => number | Get total content size | | getViewportSize | () => number | Get viewport size | | focusItemAtIndex | (index: number, options?: { ensureVisible?: boolean }) => void | Focus item at specific index | | getRange | () => VirtualScrollRange | Get current range information |

VirtualScrollRange

| Property | Type | Description | | ---------- | ------ | ------------- | | renderingStartIndex | number | Index of the first item being rendered (including overscan) | | renderingEndIndex | number | Index of the last item being rendered (including overscan) | | visibleStartIndex | number | Index of the first fully or partially visible item | | visibleEndIndex | number | Index of the last fully or partially visible item | | scrollPosition | number | Current scroll position in pixels | | totalHeight | number | Total height of the scroll content |

Advanced Usage

With Ref and Scroll Control

import { ScrollBarThumbOverlayRenderProps, VirtualScroll, VirtualScrollHandle } from '@aiquants/virtualscroll'
import { useCallback, useRef, useState } from 'react'

const items = Array.from({ length: 100000 }, (_, index) => ({
  id: index,
  text: `Item ${index}`,
  height: (index % 20) * 2 + 30,
}))

const getItem = (index: number) => items[index]
const getItemHeight = (index: number) => items[index].height

function AdvancedExample() {
  const virtualScrollRef = useRef<VirtualScrollHandle>(null)
  const [visibleStartIndex, setVisibleStartIndex] = useState(0)

  const scrollToTop = () => {
    virtualScrollRef.current?.scrollTo(0)
  }

  const scrollToIndex = (index: number) => {
    // Scroll to item 500, aligning it to the center of the viewport
    virtualScrollRef.current?.scrollToIndex(index, { align: "center" })
  }

  const handleRangeChange = useCallback((
    _renderingStartIndex: number,
    _renderingEndIndex: number,
    visibleStart: number,
    _visibleEndIndex: number,
    _scrollPosition: number,
    _totalHeight: number,
  ) => {
    setVisibleStartIndex(visibleStart)
  }, [])

  const renderThumbOverlay = useCallback((props: ScrollBarThumbOverlayRenderProps) => {
    if (!(props.isDragging || props.isTapScrollActive)) {
      return null
    }
    const activeItem = items[visibleStartIndex]
    const label = activeItem ? activeItem.text : `Item ${visibleStartIndex}`

    return (
      <div
        className="pointer-events-none absolute flex items-center"
        style={
          props.orientation === 'vertical'
            ? { top: props.thumbCenter, left: -14, transform: 'translate(-100%, -50%)' }
            : { left: props.thumbCenter, top: -14, transform: 'translate(-50%, -100%)' }
        }
      >
        <div className="rounded-full border border-slate-200 bg-white px-2 py-1 text-xs font-medium text-slate-700 shadow-md">
          {label}
        </div>
      </div>
    )
  }, [visibleStartIndex])

  return (
    <div>
      <div>
        <button onClick={scrollToTop}>Scroll to Top</button>
        <button onClick={() => scrollToIndex(500)}>Scroll to Item 500</button>
      </div>
      <VirtualScroll
        ref={virtualScrollRef}
        itemCount={items.length}
        getItem={getItem}
        getItemHeight={getItemHeight}
        viewportSize={400}
        onRangeChange={handleRangeChange}
        scrollBarOptions={{
          renderThumbOverlay: renderThumbOverlay
        }}
      >
        {(item, index) => <ItemComponent item={item} index={index} />}
      </VirtualScroll>
    </div>
  )
}

Custom Scrollbar Styling

<VirtualScroll
  itemCount={items.length}
  getItem={getItem}
  getItemHeight={getItemHeight}
  viewportSize={400}
  className="custom-virtual-scroll"
>
  {(item, index) => <ItemComponent item={item} index={index} />}
</VirtualScroll>

<style>
.custom-virtual-scroll .scrollbar {
  background-color: #f0f0f0;
}

.custom-virtual-scroll .scrollbar-thumb {
  background-color: #007acc;
  border-radius: 4px;
}
</style>

Tap Scroll Circle Configuration

The auxiliary tap scroll circle can replace the native scrollbar for large datasets while remaining easy to control:

  • Adaptive speed scaling automatically ramps up to a 120× multiplier as itemCount grows (trillions supported).
  • Manual overrides let you clamp or extend speed via maxSpeedMultiplier when you need deterministic behavior.
  • Exponential capping enables distance-sensitive ceilings with maxSpeedCurve, blending gentle near-threshold drag with high-speed travel at extended distances. Use easedOffset to slightly raise the entry-level speed without changing the ceiling.
  • Full layout control (size, offsetX, offsetY) keeps the circle accessible on both desktop and touch devices.
  • Visibility tuning exposes an opacity knob so you can match subdued or high-contrast UI themes.
import { VirtualScroll } from "@aiquants/virtualscroll"

const items = Array.from({ length: 1_000_000_000_000 }, (_, index) => ({
  id: index,
  text: `Record ${index}`,
  height: 42,
}))

export function UltraFastExample() {
  return (
    <VirtualScroll
      itemCount={items.length}
      getItem={(index) => items[index]}
      getItemHeight={() => 42}
      viewportSize={480}
      scrollBarOptions={{
        tapScrollCircleOptions: {
          maxSpeedMultiplier: 80, // Optional: override adaptive speed when needed
          maxSpeedCurve: {
            exponentialSteepness: 6,
            exponentialScale: 80,
            easedOffset: 0.1,
          },
          offsetX: -96,
          opacity: 0.85,
        }
      }}
    >
      {(item) => <div style={{ height: item.height }}>{item.text}</div>}
    </VirtualScroll>
  )
}

Performance Tips

  1. Memoize callback functions: Use useCallback for getItem and getItemHeight
  2. Optimize item rendering: Memoize item components when possible
  3. Adjust overscan count: Balance between smooth scrolling and memory usage
  4. Consider item height consistency: More consistent heights provide better performance

Browser Support

  • Chrome 88+
  • Firefox 87+
  • Safari 14+
  • Edge 88+

License

MIT