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

zero-scroll

v0.1.0

Published

Virtual scroll engine with zero DOM measurement. Fenwick tree + composable height providers.

Readme

zero-scroll

Virtual scroll engine with zero DOM measurement in the scroll hot path.

Uses a Fenwick tree (Binary Indexed Tree) for O(log n) offset lookups. Pre-compute item heights once, then every scroll frame is pure arithmetic - no offsetHeight, no getBoundingClientRect, no layout thrashing.

Performance

| Items | Per scroll frame | Setup (one-time) | |-------|-----------------|-------------------| | 1,000 | 0.004ms | 0.9ms | | 100,000 | 0.001ms | 1.7ms | | 1,000,000 | 0.0003ms | 21ms |

Compared to DOM measurement at 1,000 items:

  • 43x faster than DOM batch (write all, read all)
  • 114x faster than DOM interleaved (layout thrashing)
  • 103x faster per-scroll than TanStack-style estimate+measure

Install

npm install zero-scroll

Quick start

Framework-agnostic (core)

import { PrefixSum, computeWindow, FixedHeightProvider } from 'zero-scroll'

// 1. Create Fenwick tree and populate heights
const prefixSum = new PrefixSum(10000)
for (let i = 0; i < 10000; i++) {
  prefixSum.update(i, itemHeights[i])
}

// 2. On every scroll event - pure math, zero DOM
const { items, totalHeight } = computeWindow(
  scrollTop,     // current scroll position
  viewportHeight, // container height
  prefixSum,
  10000,
  3              // overscan
)

// 3. Render only visible items
items.forEach(({ index, offset, height }) => {
  // position item at offset with given height
})

React

import { useVirtualScroll, FixedHeightProvider } from 'zero-scroll/react'

function MyList({ data }) {
  const scrollRef = useRef(null)
  const provider = useMemo(() => new FixedHeightProvider(50), [])

  const { items, totalHeight } = useVirtualScroll({
    count: data.length,
    heightProvider: provider,
    scrollRef,
  })

  return (
    <div ref={scrollRef} style={{ height: 600, overflow: 'auto' }}>
      <div style={{ height: totalHeight, position: 'relative' }}>
        {items.map(item => (
          <div key={item.index} style={{
            position: 'absolute',
            transform: `translateY(${item.offset}px)`,
            height: item.height,
          }}>
            {data[item.index]}
          </div>
        ))}
      </div>
    </div>
  )
}

With <VirtualList> component

import { VirtualList, FixedHeightProvider } from 'zero-scroll/react'

const provider = new FixedHeightProvider(50)

<VirtualList
  items={data}
  heightProvider={provider}
  renderItem={(item, index) => <div>{item.text}</div>}
  style={{ height: 600 }}
/>

Height providers

Heights must be known upfront. This is the core design constraint that enables zero-DOM scrolling.

FixedHeightProvider

All items have the same height.

const provider = new FixedHeightProvider(50)

TextHeightProvider

Uses pretext for pure-math text measurement. Pretext is an optional peer dependency - inject it, don't import it.

import { TextHeightProvider } from 'zero-scroll'
import * as pretext from '@chenglou/pretext'

const provider = new TextHeightProvider(
  (index) => ({
    text: items[index].text,
    font: '16px Inter',
    lineHeight: 20,
    maxWidth: containerWidth,
    padding: { top: 8, bottom: 8 },
  }),
  pretext // injected, not imported by zero-scroll
)

CompositeHeightProvider

Mix different providers for different item types (e.g., text messages + image cards + date separators).

import { CompositeHeightProvider, FixedHeightProvider, TextHeightProvider } from 'zero-scroll'

const textProvider = new TextHeightProvider(getDescriptor, pretext)
const separatorProvider = new FixedHeightProvider(32)

const provider = new CompositeHeightProvider(
  (index) => items[index].type === 'separator' ? separatorProvider : textProvider,
  [textProvider, separatorProvider] // all sub-providers for invalidateAll()
)

Custom provider

Implement the HeightProvider interface for any height calculation logic.

const provider = {
  getHeight(index) { return myHeightCalculation(index) },
  invalidate(index) { /* clear cache for index */ return true },
  invalidateAll() { /* clear all caches */ },
}

Scroll anchoring

When item heights change above the viewport, the scroll position is automatically adjusted to prevent visual jumping.

import { captureAnchor, restoreAnchor } from 'zero-scroll'

// Before height change
const anchor = captureAnchor(scrollTop, prefixSum)

// ... update heights ...
prefixSum.update(index, newHeight)

// Restore scroll position
scrollContainer.scrollTop = restoreAnchor(anchor, prefixSum)

API

Core

| Export | Description | |--------|-------------| | PrefixSum | Fenwick tree for O(log n) offset queries and updates | | computeWindow() | Pure-math visible item calculation | | HeightCache | Syncs height providers with PrefixSum | | captureAnchor() / restoreAnchor() | Scroll position stabilization |

Providers

| Export | Description | |--------|-------------| | FixedHeightProvider | Constant height for all items | | TextHeightProvider | Pretext-based text measurement | | CompositeHeightProvider | Mix multiple providers |

React (zero-scroll/react)

| Export | Description | |--------|-------------| | useVirtualScroll() | Hook with scroll sync, anchoring, invalidation | | VirtualList | Convenience component with ref API |

Design philosophy

  • No DOM measurement fallback. You must know heights upfront. This constraint is what makes zero-DOM scrolling possible.
  • Pretext is optional. Core has zero dependencies. React is an optional peer dep.
  • O(log n) everything. Fenwick tree gives O(log n) for both queries and updates.
  • React re-renders only when visible range changes, not on every scroll pixel.

Inspired by

chenglou/pretext - Pure TypeScript text measurement that bypasses DOM reflow. Zero-scroll applies the same "measure once, math forever" philosophy to virtual scrolling.

License

MIT