虚拟滚动是处理万级+列表的必选项:它让页面永远只操作20~50个真实dom节点,其余靠高度占位和位置偏移“模拟”存在;核心三步是撑高(用空容器设总高度)、算范围(根据scrolltop实时计算起始索引与缓冲区)、动态换(仅渲染可视范围并用transform精确定位)。

直接渲染十万条数据会卡死页面——DOM 节点爆炸、内存飙升、滚动掉帧,首次渲染动辄数秒,FPS跌破10。虚拟滚动不是“锦上添花”,而是处理万级+列表的必选项:它让页面永远只操作20~50个真实 DOM 节点,其余靠高度占位和位置偏移“模拟”存在。
核心就三件事:撑高、算范围、动态换
虚拟滚动不靠删数据,靠“骗”浏览器:
-
撑高:用一个空容器(比如
<div>)设置总高度 = 数据条数 × 单项高度,比如 100,000 × 60px = 6,000,000px,滚动条就能正常出现,用户感知不到数据被“裁剪” <li> <strong>算范围</strong>:监听容器 <code>scroll事件,根据scrollTop和可视区域高度,实时算出当前该显示哪几条——起始索引 =Math.floor(scrollTop / itemHeight),再加缓冲区(通常 ±3~5 条),避免滚动时闪白 -
动态换:只把算出范围内那几十条数据生成 DOM,插入内容区;用
transform: translateY(偏移量)精确定位每一项位置(比 top/padding 更高效,不触发重排) - 固定高度(推荐入门):每条高度完全一致(如 60px 行高+内边距),计算简单、无回流、可预测性强。适合表格、卡片列表、日志等结构化内容
-
动态高度(进阶需求):文本长度不一、图片加载异步、折叠展开等导致高度变化。需借助
Pretext等纯 JS 高度计算库,或采样测量 + 缓存策略,避免滚动中反复创建临时 DOM 触发重排 - 缓冲区必须加:只渲染刚好可视的几条,快速滚动时会露白。上下各预留 3~5 条,让 DOM 更新有余量
-
用
DocumentFragment批量插入:避免循环中多次appendChild引发重复重排;先拼好再一次性挂载 -
节流滚动监听:不用
debounce,用requestIdleCallback或passive: true的事件监听,在空闲帧更新,不阻塞滚动 -
销毁与复用 DOM:不要每次
innerHTML = ''清空——保留节点引用,只改textContent和style.transform,减少 GC 压力 -
原生无依赖:
vue-virtual-scroller(Vue 生态)、react-window(React)、solid-virtual-scroll(SolidJS)都提供开箱即用的高性能实现 -
纯 JS 方案:如
virtual-scroller(npm 包),核心逻辑不到 200 行,支持动态高度、上拉加载、移动端惯性滚动,可商用 -
框架内建能力:Vue 3 的
v-for配合key精确复用 +getBoundingClientRect()自行计算,也能做出轻量版,适合定制化强的后台系统
固定高度 vs 动态高度:选对方案少踩坑
大多数场景建议从固定高度起步,稳定且性能最优:
关键细节决定是否真流畅
光有逻辑不够,这些实操点常被忽略但直接影响体验:
别硬造轮子:轻量库够用就上
如果你项目不强依赖某框架,几个成熟方案可直接集成:











