what
虚拟滚动是一种优化长列表性能的技术,它只渲染可见部分的列表项,而不是渲染整个列表。
本质上就是 按需渲染。
核心
- 根据 可见区域高度 和 容器滚动位置,计算出需要渲染的列表项,而不渲染其他列表项。
- 没有原生的滚动,通过js模拟的滚动:并非真实的滚动,而是重新计算元素的位置
好处
大大减少DOM操作,减少渲染时间和内存占用。从而解决页面加载慢、卡顿等问题,改善用户体验。
key-point
已知
容器高度 和 消息列表
需计算
- 渲染的数据长度
- 渲染的起始位置
计算时机
- 监听滚轮时间,得到delaY,即滚轮的滚动量
举例
如直播间的聊天室
代码实现
注意,这里是伪代码,详细参考
class VirtualScroll {
constructor({ el, list, itemElementGenerator, itemHeight }) {
/**
* 1,入参赋值
*/
this.$list = el; // 视口元素
this.list = list; // 需要展示的列表数据
this.itemHeight = itemHeight; // 每个列表元素的高度
this.itemElementGenerator = itemElementGenerator; // 列表元素的DOM生成器
/**
* 2,初始化一些计算出的备用数据
*/
// 重新构造列表
this.mapList();
// 设置一些基础样式,计算 出容器高度和内容高度
this.initContainer();
/**
* 3,绑定监听函数
*/
this.bindEvents();
}
initContainer() {
this.containerHeight = this.$list.clientHeight;
this.$list.style.overflow = 'hidden';
this.contentHeight = sumHeight(this._list);
}
mapList() {
this._list = this.list.map((item, i) => ({
height: this.itemHeight,
index: i,
item: item,
}));
}
/**
* 4,监听scroll,计算滚动量,触发渲染
*/
handleScroll(e) {
e.preventDefault();
let offset = 0;
const scrollSpace = this.contentHeight - this.containerHeight;
offset += e.deltaY;
offset = Math.min(y, scrollSpace);
// 触发渲染
this.render(offset);
}
/**
* 5,核心方法:render
*/
render(offset) {
// 画图理解
const headIndex = findIndexOverHeight(this._list, offset);
const tailIndex = findIndexOverHeight(
this._list,
offset + this.containerHeight
);
// 上方半可见的元素的偏移量
this.renderOffset = offset - sumHeight(this._list, 0, headIndex);
this.renderList = this._list.slice(headIndex, tailIndex + 1);
// 绘制,插入
const $listWp = document.createElement('div');
this.renderList.forEach((item) => {
const $el = this.itemElementGenerator(item);
$listWp.appendChild($el);
});
$listWp.style.transform = `translateY(-${this.renderOffset}px)`;
this.$list.innerHTML = '';
this.$list.appendChild($listWp);
}
bindEvents() {
this.$list.addEventListener('wheel', this.handleScroll);
}
}
// 找到第一个累加高度大于指定高度的序号,针对不定高度的item
export function findIndexOverHeight(list, offset) {
let currentHeight = 0;
for (let i = 0; i < list.length; i++) {
const { height } = list[i];
currentHeight += height;
if (currentHeight > offset) {
return i;
}
}
return list.length - 1;
}