虚拟滚动的核心是只渲染可视区域内的元素,通过监听滚动位置计算起始/结束索引,用绝对定位和 translatey 模拟滚动,保持容器总高度但仅挂载可见项,避免 dom 过载与重排重绘。

虚拟滚动的核心是只渲染可视区域内的元素,而不是整个长列表,从而大幅减少 DOM 节点数量和重排重绘开销。关键不在于“加载”,而在于“按需渲染”——数据可以一次性获取,但 DOM 只挂载当前可见的几行。
计算可视区域与滚动偏移
监听容器的 scroll 事件,结合容器高度、滚动位置(scrollTop)和每项固定高度,就能算出当前应显示的起始索引和数量:
- 假设每项高度为
itemHeight = 50,容器高containerHeight = 400,则最多显示约Math.ceil(containerHeight / itemHeight)行(比如 8 行) - 起始索引
startIndex = Math.floor(scrollTop / itemHeight) - 结束索引
endIndex = startIndex + visibleCount,再做边界截断(不能超过数据总长度)
用绝对定位模拟滚动位置
整个列表容器仍保留完整高度(totalHeight = data.length * itemHeight),但内部只渲染 [startIndex, endIndex) 的节点,并通过 transform: translateY(...) 或 top 把它们“摆到正确视觉位置:
- 第
i条真实数据对应的渲染节点,其style.transform = `translateY(${i * itemHeight}px)` - 这样滚动时 DOM 不增删,只是移动已有节点,避免 layout thrashing
- 务必给容器设
overflow-y: auto,并确保子元素脱离文档流(如position: absolute或用 transform)
处理动态高度或交互反馈
若列表项高度不固定,或需要支持点击、悬停等交互,需额外策略:
- 预存每项高度(如首次渲染后缓存
heightMap[i]),用前缀和数组快速计算某项的累计偏移 - 对用户操作(如点击某行)要映射回原始数据索引:根据点击元素的
offsetTop和已知高度表反查逻辑索引 - 可配合
IntersectionObserver做懒加载图片或动画,但不要用它替代虚拟滚动主逻辑
推荐轻量实现方式
不依赖框架时,手写 100 行内可完成核心逻辑;用现成库更稳:
- react-window(React)或 vue-virtual-scroller(Vue)已处理好 resize、键盘导航、动态高度等边界情况
- 纯 JS 方案可用 virtuoso(支持无限滚动+虚拟滚动+可访问性)
- 避免自己实现“滚动加载更多”(infinite scroll)来代替虚拟滚动——后者解决的是渲染卡顿,前者解决的是数据延迟,二者目标不同
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











