能,但前提是正确实现:滚动容器需设固定高度和overflow-y: auto;索引映射必须用二分查找而非线性遍历;确保ref指向正确容器,避免全量渲染。

能,但前提是正确实现。虚拟列表不是“开了就灵”的开关,它解决的是 DOM 节点爆炸导致的渲染卡顿,而不是数据量大本身——如果滚动容器没设固定高度、索引映射用线性遍历、或者还偷偷渲染了全量节点,那它不仅不解决问题,还会让页面更卡。
滚动容器必须设固定高度 + overflow-y: auto
这是最基础也最容易被忽略的样式约束。虚拟列表靠 scrollTop 计算可视区域起始索引,而浏览器只有在容器有明确滚动上下文时才提供可靠 scrollTop 值。
- 错误写法:
height: auto或min-height: 600px—— 容器高度随内容撑开,scrollTop失效或跳变 - 正确写法:
height: 600px; overflow-y: auto(或scroll),且该容器不能是position: static的嵌套子元素(否则可能被父级裁剪) - Vue/React 中若用 ref 绑定滚动容器,要确保 ref 指向的是这个带固定高度和
overflow的元素,而非外层 wrapper
索引映射必须用二分查找,不能 for 循环遍历
当数据量到 10w 条,每个滚动帧都执行 for (let i = 0; i 找起始项,CPU 直接拉满。虚拟列表的“常量级渲染”优势全毁在这一步。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 前提:所有列表项高度需可预测(等高最简单;变高则需缓存每项高度并构建前缀和数组)
- 等高场景:起始索引 =
Math.floor(scrollTop / itemHeight) - 变高场景:对高度前缀和数组(如
[0, 52, 108, 160, ...])调用binarySearch( scrollTop ),时间复杂度从 O(n) 降到 O(log n) - 别手写二分——用
Array.prototype.findIndex是线性的;Lodash 的sortedIndex或自己封装一个带边界检查的二分函数更稳妥
别混用非虚拟节点,尤其警惕 slot、v-model 和 position: sticky
框架组件(如 vue-virtual-scroller 的 RecycleList)会复用 DOM 节点。一旦你在模板里写了非受控内容,或依赖节点生命周期通信,就会出问题。
-
slot内容若含v-model,输入框焦点/值可能在滚动后错位——因为节点被复用,但 Vue 的响应式绑定没重连 -
ref获取的 DOM 元素,在滚动后指向的可能是另一个数据项,直接操作el.focus()会失焦 -
position: sticky在 transform 移动的虚拟列表里完全失效——sticky 依赖最近的滚动祖先,而虚拟列表靠transform: translateY()位移内容,滚动容器本身没动 - 替代方案:
position: absolute+ 动态top计算,或把 sticky 元素提到虚拟列表容器外部单独定位
占位高度必须精确,骨架屏别用真实 DOM 模拟
虚拟列表靠一个「总高度占位」元素撑开滚动条长度,它的值 = 所有数据项高度之和。这个值不准,滚动条拖拽位置、滚动到底部判断、甚至 scrollTo 都会偏移。
- 等高时:直接
totalHeight = list.length * itemHeight - 变高时:必须缓存每项高度(首次渲染后通过
getBoundingClientRect测量并存入 Map),再累加;不能每次滚动都重测 - 骨架屏不要为每个 item 渲染 3 个
<div class="skeleton"> —— 这等于又造了一堆 DOM,违背虚拟化初衷;应只渲染一个 <code><div class="item-placeholder">,用 CSS 动画模拟加载态<li>服务端渲染(SSR)时,占位高度必须和服务端计算一致,否则 hydration 后高度突变,滚动条跳动</li> <p>真正难的不是写个 <code>createItem函数,而是让高度缓存、索引映射、DOM 复用、滚动事件节流这四者严丝合缝。少一个环节,十万条数据下,用户滑动时的掉帧就藏在你没打日志的那个handleScroll里。










