Rendering 10,000 rows without freezing the browser
Large tables bring the interface to its knees when we try to paint everything at once. See how virtualization, intersection observers, and native Web Components keep 60 fps even with massive lists.
You have probably seen a table with thousands of records turn a smooth page into a slideshow. The framework is rarely to blame: it is the browser trying to build tens of thousands of DOM nodes, compute layout, and paint everything in a single frame. In this article we will break the problem into parts and solve each one with tools that already ship with the platform.
The giant DOM problem
Every cell that enters the tree costs memory and layout time. With
10,000 rows and six columns, that is 60,000 nodes in the table alone.
The browser has to measure, position, and paint all of them, even the ones off
screen. The user pays that cost in stutters.
The golden rule: the DOM should only contain what the eye can see. Everything below the fold is waste until the moment it becomes a pixel.
List virtualization
The core idea is to render only the visible window plus a few extra rows of slack. As scrolling advances, we recycle the same nodes and update their content instead of creating new elements. A spacer sets the total height so the scrollbar stays accurate.
Calculating the visible window
With a fixed row height, the math is straightforward: the first visible index is the scroll offset divided by the row height.
Recycling the nodes
Instead of removing and recreating rows, we keep a fixed pool and move each one with
transform: translateY(). Transforms do not trigger reflow, only compositing,
so scrolling stays smooth.
- Reuse the nodes, do not destroy them on every frame.
- Use
translateYinstead oftopto position them. - Update content via
textContent, avoidinginnerHTML.
IntersectionObserver and scroll-spy
To react to what enters and leaves the screen without listening to every scroll
event, IntersectionObserver is the right tool. It schedules the callbacks off
the critical scrolling thread. The very
<pura-scroll-spy> component in this article uses that API to highlight the
index item on the right.
Measuring for real
Optimization without measurement is guesswork. Before and after each change, open the
Performance panel and look at the long frames. The goal is simple: no frame should
exceed 16.6 ms.
- Record a profile while scrolling the list all the way to the bottom.
- Identify the long tasks flagged in red.
- Confirm that layout and paint shrank after virtualization.
Conclusion
Huge lists do not have to be slow. Render only what is visible, recycle nodes,
delegate visibility detection to IntersectionObserver, and measure every
step. The platform already delivers everything you need, without heavy dependencies.