虚拟列表优化核心是只渲染20–50个真实节点,用transform: translateY定位、contain: layout paint隔离渲染、passive: true滚动监听,并确保容器高度固定、scrollTop可信、列表项高度可预测。

直接渲染上万条 <li> 或 <tr> 是滑动卡顿的根源——浏览器不是被 JS 拖垮的,而是被 DOM 树本身压死的。真正有效的优化,是把可视区域外的节点彻底移出渲染流,而不是“藏起来”或“用 CSS 优化”。
<h3>为什么 display: none / visibility: hidden 不解决问题</h3>
<p>这两种方式只是让元素不可见,但 DOM 节点仍在文档中,浏览器仍要为每个节点计算布局(layout)、生成绘制列表(paint)、维护层叠上下文(stacking context)。10 万条 <code><div> 即使全 <code>display: none,DevTools 里 DOM 节点数照旧破万,Performance 面板 layout 时间持续红条。
- DOM 节点数不降,内存占用和解析开销不变
-
visibility: hidden还会保留占位,可能影响父容器尺寸计算 - 滚动监听中反复切换
display属性,等于每帧都触发重排(reflow)
必须用 transform: translateY() 定位,禁用 top/margin/padding
虚拟列表的核心是只渲染约 20–50 个真实节点,靠定位“模拟”滚动位置。一旦用 top、marginTop 或 paddingTop 推动内容,每次滚动都会强制浏览器重新计算整个容器内所有元素的几何位置。
- 正确做法:所有列表项设
position: absolute,用transform: translateY(${offset}px)移动 - 容器必须加
contain: layout paint,显式告诉浏览器“不可见区域无需参与布局与绘制” - 避免在列表项内使用
width: fit-content、flex: 1等需回溯父级尺寸的属性,它们会破坏contain效果
滚动监听必须 passive: true,且不能依赖 scrollTop 实时算索引
Chrome 对未声明 { passive: true } 的 scroll 监听器会强制同步执行 handler,哪怕你只读 scrollTop,也会打断 requestAnimationFrame 流程,导致掉帧。
立即学习“前端免费学习笔记(深入)”;
- 监听写法必须是:
window.addEventListener('scroll', handler, { passive: true }) -
scrollTop值必须来自带固定高度的容器(如style="height: 600px; overflow-y: auto;"),不能用document.documentElement.scrollTop—— 后者在嵌套滚动或 flex 容器里常返回0或NaN - 等高列表可用
Math.floor(scrollTop / itemHeight);只要存在任意一项高度不同(比如含图片、展开态描述),就必须预构建前缀和数组 + 二分查找,否则findIndex在 10 万条数据下主线程直接冻结
自定义元素中禁止 innerHTML 渲染长列表
在 connectedCallback 里拼字符串再赋值给 innerHTML,等于每次滚动或更新都触发完整 DOM 解析 + 重建,性能随数据量线性恶化。1000 条就开始卡,1 万条基本无法交互。
- 必须用
<template></template>+template.content.cloneNode(true)克隆结构,不是template.innerHTML(后者为空字符串) - 克隆后立即绑定事件,别指望事件委托穿透 Shadow DOM
- 若用 Shadow DOM,
this.attachShadow({ mode: 'open' })必须在constructor或connectedCallback中调用,否则后续插入失败
最易被忽略的一点:所有“可视区域计算”都建立在容器高度可控、scrollTop 可信、列表项高度可预测这三点之上。任一环节失控(比如父容器用了 min-height、列表项含未加载图片、滚动容器被 overflow: hidden 截断),整个虚拟逻辑就会错位甚至崩溃。先稳住这三条,再谈优化。



















