fw-10 — Analysis

Required invariants

  1. Measurements are cached by item identity, not by index. Indices shift; identities do not.
  2. A measurement correction above the viewport adjusts scroll offset, or content jumps under the user's finger.
  3. Reads are batched separately from writes, or measurement becomes forced synchronous layout.
  4. Total height is derivable in better than O(n) — a prefix-sum or Fenwick structure.
  5. Removed rows release focus and listeners, or recycling leaks.

The circularity at the centre

You cannot know total height without measuring every row; you cannot measure a row without rendering it. The resolution is estimate → measure → correct, and step "correct" is what separates a working virtualizer from a demo. It is scroll anchoring (bi-08) implemented by hand — which is precisely why the browser ships its own.

At 100,000 rows, a naive running total recomputed per measurement is O(n) per update; a Fenwick tree is O(log n). That is the difference between smooth and unusable.

Why measurement is expensive

Measuring reads geometry, which forces style and layout to be current. Per row, per frame, interleaved with writes, that is the forced-synchronous-layout bug at scale. Mitigations in preference order: ResizeObserver (delivered inside the rendering steps, after layout), batched read-then-write, IntersectionObserver for visibility, and content-visibility: auto with contain-intrinsic-size.

What virtualization breaks

BreaksMitigation
Find-in-pagenone reliable — a genuine loss
Screen-reader navigationaria-setsize / aria-posinset, a real grid role
Anchor links, scrollIntoViewroute through your own index
Tab order and focusmanage focus on recycle
Text selection across rowsrarely fixable
Printrender an unvirtualized print view
Browser scroll restorationmanual restoration

aria-setsize is the pattern again: an author-supplied promise substituting for information the runtime cannot observe.

The judgement

Virtualization trades platform behaviours you got for free against render performance. Right for a data grid; almost certainly wrong for a documentation page. An engineer who reaches for it by default has not priced the right-hand column. Before adopting it, check whether content-visibility: auto alone is sufficient — at a couple of thousand rows it frequently is, and it keeps every row in the DOM.