4、虚拟滚动技术:处理万级数据点的列表与表格
做数据可视化大屏,最怕什么?
我最怕产品经理轻飘飘来一句:「这个表格也就几万行数据,你优化一下呗。」
几万行?浏览器直接给你卡成PPT。你想想看,一个页面里塞了上万条DOM节点,光是渲染就要几百毫秒,滚动起来更是噩梦。我早期有个项目,客户现场演示时,表格一滚动,整个大屏直接白屏了——嗯,那场面,尴尬到脚趾抠地。
后来我学乖了。遇到这种场景,直接上虚拟滚动。
4.1 虚拟滚动到底是个啥?
说白了,虚拟滚动就是「只渲染你看得见的部分」。
你屏幕高度就那么大,一次能显示50行数据,那我只渲染这50行。剩下的几万行?先存着,等你滚到了再说。
这样做的好处很明显:
- DOM节点数量大幅减少——从几万个降到几十个
- 渲染性能飙升——首屏加载秒开
- 内存占用降低——浏览器不再崩溃
核心原理:监听滚动事件 → 计算当前可视区域 → 只渲染该区域内的数据 → 用绝对定位模拟滚动条高度
4.2 我自己常用的实现方案
我习惯把虚拟滚动分成三种场景,每种场景选不同的库:
| 场景 | 推荐方案 | 适用数据量 |
|---|---|---|
| 简单列表 | react-window / vue-virtual-scroller | 1万-10万 |
| 复杂表格 | ag-Grid(虚拟滚动模式) | 10万-100万 |
| 自定义大屏组件 | 自己手写虚拟滚动 | 灵活控制 |
我个人最常用的是 react-window,轻量、稳定、文档清晰。如果你用Vue,vue-virtual-scroller 也不错。
4.3 手写一个简易虚拟滚动
有时候库不够灵活,比如大屏上要自定义滚动条样式,或者要跟ECharts联动。这时候我建议自己写一个。
核心代码其实不长:
// 伪代码,理解核心逻辑即可
class VirtualScroll {
constructor(container, data, itemHeight) {
this.container = container;
this.data = data;
this.itemHeight = itemHeight;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
this.startIndex = 0;
this.endIndex = this.visibleCount;
// 监听滚动
container.addEventListener('scroll', () => {
this.startIndex = Math.floor(container.scrollTop / itemHeight);
this.endIndex = this.startIndex + this.visibleCount;
this.render();
});
}
render() {
// 只渲染 startIndex 到 endIndex 的数据
const visibleData = this.data.slice(this.startIndex, this.endIndex);
// 用绝对定位把每一行放到正确的位置
visibleData.forEach((item, index) => {
const row = document.createElement('div');
row.style.position = 'absolute';
row.style.top = (this.startIndex + index) * this.itemHeight + 'px';
row.textContent = item.name;
this.container.appendChild(row);
});
}
}
小技巧:别忘了在滚动时复用DOM节点,而不是每次都创建新的。我一般会维护一个「节点池」,滚动时只更新内容,不增删节点。
4.4 避坑指南
虚拟滚动看着简单,实际坑不少。我踩过的几个:
我曾经踩过的坑:
- 滚动闪烁——原因是滚动事件触发太频繁,渲染跟不上。解决方案:加个requestAnimationFrame做节流。
- 行高不一致——如果每行高度不同,计算位置会出错。我一般用「预估高度+动态修正」的策略。
- 搜索/筛选后数据变化——虚拟滚动依赖数据索引,数据变了要重新计算起始位置。记得在数据更新时重置滚动位置。
4.5 性能对比:虚拟滚动 vs 普通渲染
我拿一个真实项目的数据做过测试,10万行数据,每行5列:
| 指标 | 普通渲染 | 虚拟滚动 |
|---|---|---|
| 首屏渲染时间 | 3.2秒 | 0.3秒 |
| DOM节点数 | 100,000+ | ~60 |
| 滚动帧率 | 15 FPS(卡顿) | 60 FPS(流畅) |
| 内存占用 | ~500MB | ~50MB |
差距一目了然。说白了,虚拟滚动就是「用计算换渲染」——多花点CPU算位置,少让浏览器去画DOM。
4.6 什么时候不适合用虚拟滚动?
嗯,这里要注意。虚拟滚动不是万能的。我遇到过几个场景,用了反而更糟:
- 数据量小于500行——没必要,普通渲染就够了
- 每行内容高度差异极大——比如有的行只有一行字,有的行有十行图,计算量太大
- 需要全量搜索或排序——虚拟滚动只渲染部分数据,全量操作需要额外处理
遇到这些情况,我建议你换个思路。比如用「分页+懒加载」,或者干脆让后端做聚合计算,只返回汇总数据。
4.7 总结一下
虚拟滚动是我做大屏性能优化时最常用的手段之一。它不复杂,但细节很多。我的建议是:
- 先评估数据量,确定是否需要虚拟滚动
- 选一个成熟的库,别自己造轮子(除非你有特殊需求)
- 注意滚动闪烁、行高不一致、数据变化这三个坑
- 配合节流、节点复用等技巧,把性能压到极致
下一章我会讲「Canvas渲染:当DOM扛不住时,我们还有画布」。到时候我会分享一个用Canvas绘制10万点散点图的实战案例,保证让你大开眼界。