The core value of Virtual List/Windowing, The essence is:
👉 Let the browser "think" the list is very large, but in fact there are very few nodes participating in Layout/Paint
1. What exactly is the virtual list “avoiding”?
If a virtual list is not used (disaster scene)
10,000 条 DOM
↓
每次滚动:
- Layout(全部参与)
- Paint(大量节点)
- Style Recalc📉 FPS crashes directly
After using virtual list
可视区 20 条 + buffer 10 条 = 30 条 DOM
↓
滚动:
- 少量 transform
- 少量 style 更新
- 几乎不 Layout✔️ Keep visible DOM counts bounded; layout cost still depends on styles, content and updates.
2. Three "pitfall points" of virtual lists (core principles)
① DOM quantity control (most important)
Complexity of Layout / Paint ≈ Number of DOM
Virtual lists only render:
[ startIndex, endIndex ]Rest:
- DOM does not exist
- Not participating in Render Tree
- Not participating in Layout/Paint
② Use "displacement" instead of "rearrangement" for scrolling
❌ Wrong approach (knowing Layout)
item.style.top = index * itemHeight + 'px'- top → affects layout
- Each item may trigger layout calculations
✅ Positioning optimization (when compositing conditions hold)
container.style.transform = `translateY(${offset}px)`- Transform positioning usually leaves document-flow layout unchanged.
- Reusable layers can reduce Layout / Paint, but compositor-only updates are conditional.
The core benefit is limiting how many DOM nodes participate in rendering.
③ Taking place but not rendering (scroll illusion)
core structure
<div class="viewport">
<div class="phantom"></div>
<div class="content"></div>
</div>.viewport {
position: relative;
overflow-y: auto;
height: 400px;
}
.phantom {
height: 100000px; /* 总高度 */
}
.content {
position: absolute;
top: 0;
left: 0;
transform: translateY(2000px);
}browser perspective
- The phantom supplies total height and can require Layout when its height or container changes.
- Content limits visible DOM and uses transform for offsets; changing content can still require paint.
👉 The scroll bars are real, but the DOM is minimal
3. Comparison of complete rendering processes (very important)
Normal list scrolling
scroll
→ scroll offset / clipping
→ Composite
→ Layout / Paint when content or geometry requires itVirtual list scrolling
scroll
→ JS calculates visible range
→ update or recycle a small number of nodes
→ Layout / Paint as needed
→ transform offset / CompositeVirtualization reduces layout and paint work without guaranteeing their absence.
4. Why does scrolling itself not layout frequently?
Browser optimization points
scroll ≠ recalculate layout
Just:
- Change scroll offset
- Crop visible area
But the problem is:
👉 When there are too many child nodes, Paint and Composite are still expensive
Virtual list directly:
- don't let them exist
5. What to do with Variable Height?
This is an advanced level.
Option 1: Fixed height (fastest)
- Best performance
- The simplest implementation
Solution 2: Highly cached (mainstream solution)
heightMap[index] = realHeight- first measurement
- Use cache directly later
- Avoid repeated
getBoundingClientRect
📌 Prevent Layout Thrashing
Solution three: IntersectionObserver (modern solution)
- IntersectionObserver reports intersections, not complete item-height measurements.
- Use ResizeObserver for size changes and cache measured heights.
- Batch measurements and updates to avoid forced Layout from interleaved reads/writes.
6. Verify Layout / Paint costs in DevTools
1️⃣Performance panel
During scrolling:
Check whether Layout / Paint decreases and DOM updates produce long tasks.
Inspect compositing costs rather than inferring performance from transform alone.
2️⃣ Paint flashing
- Very little flicker in the visible area
3️⃣ FPS meter
- Measure actual frame rate; it depends on device, refresh rate, data and application work.
7. Why do React/Vue recommend virtual lists?
React(react-window)
- Use transform
- Merge setState
- rAF scheduling
Vue(vue-virtual-scroller)
- Absolute positioning + translate
- cache height
- Reduce watcher triggers
👉 The essence is:
Controlling the DOM + using compositing layers
8. A minimalist "virtual list core algorithm" (for understanding)
const itemHeight = 40
const visibleCount = Math.ceil(viewportHeight / itemHeight)
function onScroll(scrollTop) {
const start = Math.floor(scrollTop / itemHeight)
const end = start + visibleCount + buffer
content.style.transform =
`translateY(${start * itemHeight}px)`
render(items.slice(start, end))
}9. Summary
- The virtual list renders only a small amount of DOM near the visible area.
- Significantly reduce the number of nodes participating in Layout and Paint.
- Also use transform for displacement,
- Let the rolling process mainly occur in the Composite phase,
- This maintains stable rendering performance in lists with large data volumes.