长列表无限下拉的实现(下)
还没看过上篇的小伙伴,可以先阅读《长列表无限下拉的实现(上)》。我本想沿着上篇的思路,继续使用padding
来实现,但是我实现后感觉并不流畅,于是参考了transform
的实现方式。
不定高元素虚拟列表的实现
整体思路
在上篇中实现定高虚拟列表是通过直接传入了一个值作为列表项的高度,这种方案仅适用于知道行高的情况,而在不定高列表中渲染前并不知道列表项的高度。有的小伙伴可能会想到将列表项渲染到屏幕外,对其高度进行测量并缓存,然后再将其渲染至可视区域内。这将导致渲染成本增加一倍,所以此方案并不可行。通过预估高度先行渲染再获取真实高度并缓存的方案,规避了前两种方案的缺陷。完成后的虚拟列表的效果如下图所示:
此外,手淘的一个方案是通过intersectionObserver
观察元素是否进入视口,这里不展开,具体请看《无限滚动加载解决方案之虚拟滚动(下)》。
列表项动态高度
先来看看组件化后基础使用方式:
<VirtualList :listData="data" :estimatedItemSize="100" v-slot="slotProps">
// Item 是抽离出的业务组件,slotProps.item 是通过作用域插槽拿到的私有变量
<Item :item="slotProps.item" />
</VirtualList>
这个组件共接受 4 个参数:
名称 | 类型 | 描述 |
---|---|---|
listData | Array | 传入的列表数据 |
estimatedItemSize | Number | 预估高度,单位为px |
bufferScale | Number | 缓冲区屏数,默认为1 |
height | String | 容器高度,默认100% |
在created
钩子,组件调用初始化方法initPositions
,具体代码如下:
methods: {
initPositions() {
this.positions = this.listData.map((d, index) => ({
index,
height: this.estimatedItemSize,
top: index * this.estimatedItemSize,
bottom: (index + 1) * this.estimatedItemSize,
}));
},
}
这里定义了 positions
,用于列表项渲染后存储 每一项的高度以及位置
信息。由于列表项高度不定,并且我们维护了 positions
,用于记录每一项的位置,而 列表高度
实际就等于列表中最后一项的底部距离列表顶部的位置,即this.positions[this.positions.length - 1].bottom
。渲染完成
后,获取每项的位置信息并缓存,所以使用钩子函数updated
来实现。
滚动后获取列表开始索引
的方法修改为通过缓存
获取:
getStartIndex(scrollTop = 0){
let item = this.positions.find(i => i && i.bottom > scrollTop);
return item.index;
}
由于bottom
在positions
每一项中是递增的,此处可用二分查找
优化。至此,列表项动态高度已经实现,其它的一些优化点与上篇中提到的内容相差无几。
总结
我本想再深入一些,但参考中的两篇文章无论代码还是描述都已十分详细,而我也没有另外的技术方案,因此下篇仅作为上篇的补充罢。
Demo 地址
ivestszheng/virtual-scroll-demo。