/ Docs

Stats #

PublishedUpdated Sep 16, 2026

Pure computation module for scroll statistics. Tracks velocity, computes item count and scroll progress using geometric mapping. No DOM access, no side effects.

import { createVList, createStats } from "vlist";

const list = createVList({
  container: "#app",
  item: { height: 48, template: renderItem },
  items: data,
}, plugins);

const stats = createStats({
  getScrollPosition: () => list.getScrollPosition(),
  getTotal: () => data.length,
  getItemSize: () => 48,
  getContainerSize: () => document.querySelector("#app").clientHeight,
});

list.on("scroll", () => {
  const { progress, itemCount, total } = stats.getState();
  console.log(`${progress.toFixed(0)}% — ${itemCount}/${total} items`);
});

Config #

All inputs are callbacks so the tracker always reflects the latest values without needing to be recreated when the list changes.

Option Type Description
getScrollPosition () => number The list's logical scroll position, normally list.getScrollPosition()
getTotal () => number Total number of items
getItemSize () => number Item size along the scroll axis (height for vertical, width for horizontal)
getContainerSize () => number Viewport size in px (clientHeight for vertical, clientWidth for horizontal)
getColumns () => number Column count for grid/masonry layouts. Defaults to 1

State #

stats.getState() returns:

Property Type Description
progress number Scroll progress as 0–100
velocity number Current instantaneous velocity in px/ms
velocityAvg number Running average velocity in px/ms (filtered)
itemCount number Number of items visible up to the current scroll position
total number Total number of items

Methods #

Method Description
getState() Return the current computed state. Pure read — no side effects
onVelocity(velocity) Feed a velocity sample. Call from the velocity:change event

Velocity tracking #

The stats module filters velocity samples to avoid outliers. Only values between 0.1 and 50 px/ms are included in the running average. Feed samples from the velocity:change event:

list.on("velocity:change", ({ velocity }) => {
  stats.onVelocity(velocity);
});

Grid and masonry support #

For multi-column layouts, provide getColumns so the geometric mapping correctly converts visible rows to item counts:

const stats = createStats({
  getScrollPosition: () => list.getScrollPosition(),
  getTotal: () => items.length,
  getItemSize: () => effectiveRowHeight,
  getContainerSize: () => container.clientHeight,
  getColumns: () => 4,
});

Large lists #

Every scroll mode reports an unscaled logical position, so progress and item counts follow the real content size.

  • Synthetic input (scroll.mode: "synthetic", or "auto" past the browser's element size limit): any list size reaches 100% at the end.
  • scroll.mode: "native" past the browser's element size limit (about 16 million px): the browser cannot scroll to the end of the declared content, so progress stops short of 100%. The list emits an error event with the context content:size:overflow when this happens; leave the default "auto" for lists that large.

Examples #

  • Photo Album — grid/masonry gallery with progress, velocity, and item count display
  • Scrollbar — contact list with scroll statistics