Welcome to Part 2 of our series, "Rendering 100k Rows: How Far Can the DOM Go?"
In Part 1, we explored how Pagination keeps our application snappy by capping the DOM size. But pagination is a compromise: it forces users to click through pages, breaking their reading flow and preventing native browser features like scrollbars and touch-scrolling from behaving naturally.
When developers want to avoid pagination, they often turn to Infinite Scroll (loading more items as the user scrolls to the bottom). However, standard infinite scroll suffers from the exact same fate as loading all rows upfront: the DOM continues to bloat, memory consumption climbs, and the browser eventually crawls to a halt.
How do we deliver the seamless, uninterrupted experience of scrolling through a list of 100,000 items without overloading the browser’s rendering engine?
The answer is DOM Virtualization (often referred to as Windowing or List Recycling).
The Core Concept: DOM Recycling
Instead of mounting all 100,000 rows, virtualization mounts only the tiny subset of rows that are currently visible within the user's scroll window (plus a small buffer above and below to prevent flickering).
As the user scrolls, the table doesn't create new HTML elements. Instead, it recycles the existing rows: it shifts their positions and updates their text labels dynamically.
[ Offscreen Row (Top) ] <-- Leaves viewport, recycled to bottom
┌─────────────────────────────┐
│ Row #41 (Visible) │
│ Row #42 (Visible) │ <-- Viewport Window
│ Row #43 (Visible) │
└─────────────────────────────┘
[ Offscreen Row (Bottom) ] <-- Repositioned & updated with Row #44
To the user, it feels like they are scrolling through a massive document. Behind the scenes, the browser is only rendering 10 to 20 DOM elements.
Visualizing DOM Virtualization
Drag the scroll slider below to see virtualization in action.
- The Browser Viewport is shown in the middle.
- Watch how only a few rows are rendered. As soon as a row slides out of the top, it instantly snaps to the bottom, changes its row index, and glows to show it has been recycled.
How to Build a Virtual Scroller
To make the browser believe it is scrolling a page of 100,000 items, we construct a three-layer container structure:
- Viewport (The Window): A container with a fixed height and
overflow-y: auto. - Fake Scrollbar Runway (The Height): A completely empty container inside the viewport with a height set to the total calculated height: Total Rows × Row Height (e.g.
100000 * 40px = 4000000px). This causes the browser's native scrollbar to resize accurately. - Element Wrapper (The Active Set): A container styled with
position: relativeorabsolutethat holds only the recycled rows, shifted dynamically usingtransform: translateY(Ypx).
Interactive Demo: DOM Virtualization
Below is a complete, working virtualized list rendering 100,000 rows.
Open the editor (click Edit Code), adjust the total rows count in the JavaScript script, or change the row height, and see how the list remains smooth without lagging.
Crucial Challenges with Virtualization
While DOM virtualization is highly effective, it introduces several complex developer challenges:
- Dynamic Heights and the Runway Calculation Issue: If rows have dynamic content (like wrapping text or varying comments) and variable heights, calculating the total scroll runway height becomes an offset nightmare. Since off-screen rows aren't rendered, their heights must be estimated. As these rows are scrolled into view and measured, the virtual scrollbar height changes dynamically, making the scrollbar handle jump or stutter.
- Keyboard Navigation & Search: Because only 15 rows are mounted, standard browser searches (
Ctrl+F) will not work on the list. You must implement a custom search filter programmatically. - Accessibility (a11y): Screen readers expect lists to read all elements sequentially. When elements disappear during scrolls, it confuses assistive technologies. You must use ARIA grid roles to preserve structure.
Summary: When to Virtualize
Use DOM Virtualization when you want an infinite scroll experience with a dataset ranging from 1,000 to 50,000 rows. Beyond 50,000 rows (especially with wide multi-column tables), even updating 30 elements on every scroll tick can start showing frame drops.
In Part 3, we will look at how high-frequency database applications bypass the DOM completely by drawing rows onto HTML5 Canvas. Stay tuned!