真能撑住上万条数据的局部滚动只有一种做法:固定容器高度+只渲染可视区域+用transform: translateY()定位,其余全靠占位撑高;滚动容器必须设固定高度且独立于父级布局,禁用transform/will-change/filter等干扰样式,可视索引须用二分查找,高度缓存需稳定,列表项必须用transform定位,.phantom层高度须精确匹配数据总量与行高乘积。

局部滚动不是加个 overflow-y: auto 就完事——它必须和虚拟列表逻辑绑定,否则滚动容器一卡,整个页面就卡。真能撑住上万条数据的局部滚动,只有一种做法:固定容器高度 + 只渲染可视区域 + 用 transform: translateY() 定位,其余全靠占位撑高。
滚动容器必须设固定高度且独立于父级布局
这是所有计算的前提。如果 #list-container 是 height: auto、min-height,或嵌套在 display: flex / grid 的自适应父容器里,scrollTop 会跳变、归零甚至返回 NaN,可视索引直接算错。
- 正确写法:
style="height: 600px; overflow-y: auto;",且该元素不能被父级overflow: hidden裁剪 - React 中用
useRef绑定时,ref 必须指向这个带overflow的容器,不是外层wrapper - Vue 场景下若用
v-virtual-scroll,需确认插件文档是否要求wrapper显式设高 - 禁用会干扰滚动上下文的样式:避免父级有
transform、will-change、filter,否则getBoundingClientRect()偏移不准
可视起始索引必须用二分查找,不能线性遍历
等高场景下 Math.floor(scrollTop / itemHeight) 看似简单,但只要有一项高度不同(比如含图片、富文本、折叠态),位置就会偏移。10 万条数据滚动时用 findIndex 或 for 循环,主线程立刻卡死。
- 变高场景必须构建前缀和数组,如
[0, 48, 96, 142, ...],每个值是第i项顶部距列表首的距离 - 对前缀和数组调用二分查找(不是
Array.prototype.findIndex),推荐手写带边界检查的版本或用lodash.sortedIndex - 高度缓存必须稳定:不要在
itemSize函数里读 DOM,改用Map或useMemo缓存首次测量结果 - 禁止把
items.length当作itemCount传入——必须传原始总条数,否则 filter 后索引错乱
列表项必须用 transform 定位,禁用 margin-top 和 top
margin-top 或 top 推动内容等于每帧都强制重排(reflow);而 transform: translateY() 是合成层操作,GPU 加速,不触发布局计算。Chrome 115–122 中,某些编辑器场景下 top 对 selection 坐标系更准,但普通长列表必须选 transform。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
立即学习“前端免费学习笔记(深入)”;
- 所有列表项统一用
position: absolute,style.transform = `translateY(${offset}px)` - 容器本身加
contain: layout paint,告诉浏览器“不可见区域不用绘制” - 撑高滚动条的
.phantom层必须是容器第一个子元素,高度必须精确为data.length * itemHeight(不能四舍五入,不能用offsetHeight反推) - 别混入非数据节点:广告、分组标题必须作为
type: 'ad'或type: 'group'塞进items数组,统一走索引计算
滚动监听必须加 { passive: true } 并节流到 requestAnimationFrame
原生 scroll 事件在 Chrome 中每秒可触发上百次,不加 passive 会让 Chrome 强制同步执行 handler,拖垮帧率;不节流到 requestAnimationFrame,JS 主线程持续满载,滚动必然掉帧。
- 监听时必须写成:
container.addEventListener('scroll', handleScroll, { passive: true }) -
handleScroll内部不能直接操作 DOM 或触发setState,应先记录scrollTop,再用requestAnimationFrame批量更新 - 避免在虚拟项内用
v-model或ref直接操作 DOM——节点会被复用,焦点和输入值容易错乱 - 实时追加数据后不会自动滚动到底部,需手动
container.scrollTop = container.scrollHeight,且要在 DOM 更新后执行(可用queueMicrotask或setTimeout(..., 0))
最易被忽略的是:撑高滚动条的 .phantom 层高度必须与数据总量和行高乘积完全一致,差 1px 都会导致滚动条错位或消失;还有就是非数据节点(如广告、分组标题)如果硬塞进 DOM 而不纳入虚拟索引体系,滚动过程中会出现跳变、错位、白屏——这些都不是框架 bug,是结构没对齐。


















