虚拟滚动是渲染10万行表格的必需方案,核心是仅将视口及缓冲区内的行注入dom,通过scrolltop与固定行高计算索引、position:absolute精确定位、占位元素撑开滚动条,并须处理resize、键盘导航等边界场景。

大数据量表格直接渲染 10 万行,浏览器必然卡死甚至白屏——虚拟滚动不是“可选优化”,而是必须落地的底线方案。核心就一条:只让视口内及缓冲区的行进入 DOM,其余全部剔除。
为什么 display: none 不是虚拟滚动
把所有 <tr> 都塞进 DOM,再用 <code>display: none 隐藏掉不可见行,看似省事,实则埋雷:
- 10 万行 = 10 万个 DOM 节点持续占用内存,Chrome DevTools 的 Memory 面板会立刻报警
- 每次滚动触发重排(reflow),浏览器要反复计算全部行的布局,帧率暴跌到 10fps 以下
-
visibility: hidden同样无效——节点还在,开销照旧 - React/Vue 中若没做 key 稳定或 memo 包裹,
display: none切换还会引发整块子树重渲染
scrollTop + 固定行高是计算可视索引的唯一可靠路径
别信 IntersectionObserver 主控虚拟滚动——它响应延迟、无法精确定位“当前第几行”,只适合图片懒加载。
正确做法是监听容器的 scroll 事件,靠 scrollTop 和预设的 rowHeight 算索引:
const startIndex = Math.floor(scrollTop / rowHeight); const visibleCount = Math.ceil(containerHeight / rowHeight) + 4; const rowsToRender = data.slice(startIndex, startIndex + visibleCount);
-
rowHeight必须固定(如48px),否则得缓存每行getBoundingClientRect(),性能代价翻倍 - 加
+ 4是缓冲区,防滚动过快时闪白 - 容器高度要用
clientHeight或 CSSheight,不能依赖offsetHeight(含边框) - 节流必须做,
requestAnimationFrame比throttle(16)更稳
position: absolute 占位容器是内容不跳动的前提
只渲染部分 <tr> 还不够,必须让它们“停在该在的位置”:<ul>
<li>外层容器设 <code>overflow-y: auto,且有明确 max-height
<div>,高度设为 <code>data.length * rowHeight,并 position: relative<tr> 放进这个 <code><div>,每个都加 <code>position: absolute; top: ${index * rowHeight}pxposition: relative,absolute 子元素会相对于 body 定位,一滚动就错位transform: translateY() 替代 top——某些 Chrome 版本下会触发意外合成层,更卡表头固定和键盘导航是上线前最容易崩的两个点
虚拟滚动本身能跑通,不等于能交付。这两个细节一漏,用户一缩放窗口或按 PageDown 就卡死:
- 窗口 resize 后必须重算
containerHeight和visibleCount,否则可视行数错乱 - 键盘滚动(
PageUp/PageDown)要手动同步scrollTop,并触发一次可视索引重算 - IE11 下
scrollTop在某些父容器上行为异常,得加 UA 判断 + fallback -
position: sticky表头必须确保其最近滚动祖先就是包裹<table> 的那个 <code><div>,且该 <code><div> 不能有 <code>transform或filter真正难的从来不是“怎么画出前 20 行”,而是“怎么保证缩放、键盘、触控、低配设备下,这 20 行始终精准、稳定、不重建”。











