直接渲染10万条必然卡顿,因DOM树本身造成渲染压力而非JS遍历;虚拟列表需满足容器固定高度、滚动绑定到overflow容器、列表项用absolute+transform定位三条件;等高可用scrollTop/itemHeight计算,变高须构建前缀和数组并二分查找。

为什么直接渲染10万条
浏览器渲染压力不来自JS遍历,而来自DOM树本身。DevTools里看到
- 下上万个
- 节点、Performance面板持续红条,就是典型信号。哪怕用了
key、React.memo或v-for,只要真实DOM节点数破万,layout/paint阶段就会拖垮帧率。常见错误是把“节流滚动 + 只显示前100条”当成优化——这只能缓解首屏,滚动到底部时仍要一次性挂载全部节点。
- 用
document.createElement循环追加,主线程被持续占用 - 用
innerHTML += ...触发多次重排,比逐个append更慢 - 嵌套5层
<div class="wrapper">包列表项,每次scrollTop变更都引发深层重排
虚拟列表必须满足的三个硬性条件
缺一不可,否则
transform: translateY()定位会偏移、跳变甚至失效。- 容器必须设固定高度:
style="height: 600px; overflow-y: auto;",不能用min-height或height: auto - 滚动监听必须绑定到这个带
overflow的容器,不是外层wrapper - 所有列表项统一用
position: absolute,靠transform: translateY(${offset}px)定位,禁用margin-top、top、paddingTop
Vue中若用
v-virtual-scroll,需确认文档是否要求wrapper显式设高;React中用useRef绑定的必须是该容器节点。立即学习“前端免费学习笔记(深入)”;
等高 vs 变高:位置计算不能偷懒
等高场景下
startIndex = Math.floor(scrollTop / itemHeight)可行,但只要有一项高度不同(比如含图片、折叠态、富文本),就必须构建前缀和数组并二分查找。Array.prototype.findIndex是O(n)操作,10万条数据滚动时主线程直接卡死。- 构建
positionMap = [0, 48, 96, 142, ...],每个值是第i项顶部距列表首的距离 - 用
binarySearch(positionMap, scrollTop)找可视区起始索引,时间复杂度O(log n) - 高度缓存必须持久化:首次测量后存入Map,避免滚动中反复调用
getBoundingClientRect()
HTML结构本身就能埋雷
不用框架也会卡,根源常在HTML写法。
- 用
<table>渲染长列表:浏览器必须收齐整行所有<td>才构建DOM,首屏阻塞严重 - 深层嵌套:5层
<div>包一个<li>,修改任意父级padding都会触发全树重排 - 留着
display: none的广告位、Tab面板:它们仍在DOM中参与样式计算,内存和CPU双浪费
真正有效的做法是:首屏内容尽量靠近
<body>开头,删掉所有无样式/无事件/无语义的空<div>,长列表优先用<ul>+display: grid替代表格语义。 - 用



















