不能用 display: none 或 visibility: hidden 处理 10 万行表格,因 DOM 仍驻留内存且滚动时触发重排导致卡顿;虚拟滚动需放弃动态高度、统一固定行高、配合 table-layout: fixed、正确使用 position: relative/absolute、节流滚动监听并响应尺寸变化。

为什么不能用 display: none 或 visibility: hidden 应对 10 万行表格
它们只是隐藏 DOM,不是移除——10 万行 <tr> 全在内存里,DevTools 的 Memory 面板会立刻飙红;每次 scroll 触发重排(reflow),浏览器仍要遍历全部节点计算布局,帧率常跌破 10fps。虚拟滚动的核心动作是「不创建」,不是「藏起来」。
scrollTop + 固定 rowHeight 是计算可视索引的唯一可靠路径
动态高度必须放弃:一旦 rowHeight 不固定,就只能靠 getBoundingClientRect() 缓存每行位置,而这个 API 在滚动中反复调用会触发强制同步布局(layout thrashing),比全量渲染还慢。真实业务中行高差异超 ±5px 就肉眼可见“跳行”。
- 必须统一 CSS:
height: 48px或line-height: 48px,禁用flex自适应、min-height、内容撑开等干扰 - 表格场景加
table-layout: fixed和显式列宽,防单元格内容撑高 - 计算公式必须是:
const startIndex = Math.floor(scrollTop / rowHeight),缓冲区建议 +4 行防快速滚动闪白
占位容器的 position: relative 和渲染项的 top 必须配对使用
漏掉外层 position: relative,position: absolute 的 <tr> 会相对于 body 定位,一滚动就错位飘出视口。也别用 transform: translateY() 替代 top——Chrome 115–122 版本会意外触发额外合成层,GPU 内存暴涨。
- 外层滚动容器(如
<div class="viewport">)必须设position: relative和overflow-y: auto - 占位
<div class="phantom">高度 =data.length * rowHeight,且放在该容器内 - 真实渲染的
<tbody>或<div class="list-items">设position: absolute,每行top: ${index * rowHeight}px
滚动监听必须节流,且不能只靠 requestAnimationFrame
scroll 事件在 Chrome/Firefox 中每秒可触发 60–120 次,不做控制等于每秒执行几十次 slice() + DOM 操作。仅靠 requestAnimationFrame 不够——它只保证“下一帧执行”,但无法限制调用频次,快速滚动时任务仍会堆积。
立即学习“前端免费学习笔记(深入)”;
- 推荐组合:
requestAnimationFrame+ 状态锁(isPending),确保同一帧只执行一次计算与渲染 - 容器尺寸变化(如窗口缩放)必须用
ResizeObserver重新计算visibleCount,不能依赖滚动回调重算 - 更新前务必清空旧节点:
contentEl.replaceChildren()(现代)或contentEl.innerHTML = ''(兼容 IE11)
真正让虚拟表格在线上不崩的,往往不是“怎么画出来”,而是缩放后是否重算可视行数、键盘 PageDown 是否同步滚动位置、IE11 下 scrollTop 是否取整异常——这些细节没处理好,用户第一眼就会感知卡顿。



















