虚拟滚动是渲染10万行表格的必需方案,必须通过scrolltop与固定rowheight计算可视索引、用absolute定位占位、处理表头固定和键盘导航等硬性前提。

10 万行表格直接渲染,浏览器必然卡死——虚拟滚动不是“锦上添花”,而是必须落地的底线方案。
为什么 display: none 不算虚拟滚动
把全部 <tr> 塞进 DOM,再用 <code>display: none 隐藏不可见行,看似简单,实则无效:
- 10 万行 = 10 万个 DOM 节点持续驻留内存,DevTools 的 Memory 面板立刻飙红
- 每次
scroll触发重排(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、用方向键滚动,任何一个没处理好,用户立刻感知卡顿。这些不是“附加功能”,而是虚拟滚动交付的硬性前提。











