虚拟滚动通过只渲染可视区域节点并用transform动态定位来提升长列表性能。核心是外层容器撑起总高度、内部仅渲染视口附近行、监听滚动实时计算索引与偏移,需避免重排、合理节流、固定key、禁用动画,并注意行高适配、滚动条、首屏加载和键盘导航等细节。

长列表卡顿,本质是浏览器要一次性渲染成百上千个 DOM 节点,内存占用高、重排重绘频繁。虚拟滚动不渲染“看不见”的项,只维持视口附近少量节点,性能提升立竿见影——关键不在“怎么写”,而在“怎么稳、怎么扩、怎么不翻车”。
核心原理:只渲染可视区域 + 动态定位
虚拟滚动不是隐藏元素(display: none 或 visibility: hidden),而是:
- 用一个外层容器撑起完整高度(比如总高度 = 行高 × 数据总数)
- 内部仅渲染当前可视范围内的若干行(如前后各缓存 5 行)
- 通过 transform: translateY 让内容精准“对齐”到滚动位置
- 监听 scroll / resize / resizeObserver,实时计算起始索引和偏移量
注意:不要用 top/left 定位,会触发重排;必须用 transform + will-change: transform 提升合成层。
手写简易版(50 行内可跑通)
适合快速验证或轻量场景,不依赖框架:
- 给容器设 height: 600px; overflow-y: auto;,并加 contain: strict;(隔离重绘范围)
- 计算:可视行数 = Math.ceil(容器高度 / 单行高度),起始索引 = Math.floor(scrollTop / 单行高度)
- 取数据片段:items.slice(start, start + visibleCount)
- 用 style="transform: translateY(${start * rowHeight}px)" 定位内容区
- 滚动时防抖节流(requestIdleCallback 或 setTimeout 延迟更新)
框架中推荐方案(React/Vue)
别重复造轮子,优先用成熟库,但要注意配置细节:
- React:用 react-window(轻量、API 清晰),避免 react-virtualized(包大、API 陈旧);开启 useIsScrolling 配合骨架屏降感知延迟
- Vue 3:用 vue-virtual-scroller 或更现代的 virtuoso;注意设置 itemSize 为函数以支持动态行高(需预估或异步测量)
- 统一要点:禁用 key 变化触发重渲染(固定 key)、关闭过渡动画、列表项避免内联样式和复杂 effect
避坑清单:这些细节一错就白干
真实项目里,卡顿往往来自“优化之外”的地方:
- 行高不固定?别硬写死 itemSize —— 用 Virtuoso 的 estimateSize 或提前采集前 N 条高度做平均
- 滚动条消失?给外层容器加 overflow-y: overlay(macOS)或自定义 scrollbar 样式,避免因滚动条宽度变化导致 layout shift
- 首屏白屏?服务端不渲染虚拟列表,客户端 hydration 前先展示 loading 骨架 + 预设占位高度
- 键盘导航失效?手动绑定 onKeyDown,监听 ArrowDown/Up/PageDown 等,并同步更新 scrollTop
虚拟滚动不是银弹,但它把 O(n) 渲染降到 O(k)(k≈10~20),只要逻辑清晰、边界守牢,万级列表也能丝滑到底。


















