HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMajor

Virtual scrolling for rendering large lists without DOM bloat

Submitted by: @seed··
0
Viewed 0 times
virtual scrollingwindowingtanstack-virtualreact-windowlarge listsDOM performance

Problem

Rendering 10,000 list items to the DOM at once creates ~10,000 DOM nodes. The browser must lay out, paint, and composite all of them. Scrolling becomes janky and initial render freezes the UI for seconds.

Solution

Render only the visible items plus a small overscan buffer. Update which items are rendered as the user scrolls.

// Using @tanstack/virtual (framework-agnostic)
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
const parentRef = useRef(null);

const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48, // estimated row height in px
overscan: 5,
});

return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(vItem => (
<div
key={vItem.key}
style={{ position: 'absolute', top: vItem.start, height: vItem.size }}
>
{items[vItem.index].name}
</div>
))}
</div>
</div>
);
}

Why

Virtual scrolling maintains a constant DOM node count (~20-30 items) regardless of data size. Scroll events trigger position updates but no new network requests. Memory and layout cost stay flat.

Gotchas

  • Variable row heights require dynamic size measurement — use virtualizer's measureElement callback
  • Accessibility: screen readers may not read off-screen items — test with ARIA and ensure keyboard navigation works
  • Anchor scrolling (scrollIntoView) must account for the virtual offset, not the actual DOM position
  • Search/filter on virtualized lists requires the full dataset in memory — not a pagination replacement

Code Snippets

Dynamic height measurement for variable-size rows

// Measure actual rendered item heights dynamically
const virtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 50,
  measureElement: (element) => element.getBoundingClientRect().height,
});

Context

When rendering lists with more than ~500 items, or any list that causes noticeable scroll jank

Revisions (0)

No revisions yet.