简介
虚拟滚动是一项数据列表优化技术,用于处理大量数据列表的渲染和显示。通过仅渲染可见区域的列表项,虚拟滚动显著提升了性能,减少了内存占用,并提供了更流畅的用户体验。
本文将基于 useVirtualList
、vue-virtual-scroller
常用库的实现,分析虚拟滚动列表的原理
基础概念
可视区域(Viewport)
图中的 Container容器
可视区域是指当前用户在屏幕上可以看到的部分内容。虚拟滚动只会渲染这个区域内的列表项,而不会渲染超出可视区域的部分
偏移(Offset)
图中的offset
偏移是指从列表顶部到当前可视区域顶部之间的距离,通常以像素为单位。偏移量决定了需要跳过多少列表项,以便只渲染可见的部分。
列表项高度(Item Height)
图中wrapper容器的高度
列表项高度是指每个列表项的高度,可以是固定值或动态计算值。固定高度的列表项较易处理,而动态高度的列表项需要更复杂的计算。
缓存区(Overscan)
图中 overscan
缓存区是指在可视区域的上下方多渲染的一些列表项,用于预加载即将进入可视区域的内容,从而提供更流畅的滚动体验。
内容容器(Wrapper)
图中 wrapper容器
内容容器是实际渲染列表项的内部元素,其高度或宽度通常根据整个列表的大小来设置。通过设置内容容器的大小,虚拟滚动可以生成滚动条。
useVirtualList
原理
适用于固定大小的数据列表
基本使用
通过看示例代码,发现滚动列表的具有几个要点:
containerProps
外部容器,具有固定的高度或宽度wrapperProps
内容容器,具有列表真实的高度list
当前可见区域的渲染列表itemHeight
行高度,可以静态高度,动态高度需传入函数overscan
视区上、下缓存展示的DOM节点数量
useVirtualList
的源代码如下
源代码中会返回几个参数:
- list
- scrollTo
- containerProps: {}
- wrapperProps: {}
其中可看到containerProps中,ref为绑定的DOM 引用, onScroll监听滚动事件,
其中的onScroll事件的函数回调为核心
进一步查看源代码,发现useVerticalVirtualList
和 useHorizontalVirtualList
是两个核心函数,分别处理垂直和水平方向的列表。
我们先看useVerticalVirtualList
的源代码
通过源代码可以看到几个参数的函数:
- wrapperProps 会计算出原列表的总高度以及marginTop。
height
为实际高度。目的是可以生成滚动条。marginTop
外边距。目的是为了把渲染区域的列表可以在外部容器中展示。因为,当内部容器开始进行切割只渲染可视区域的列表时,默认情况下,列表的scrollTop都会是0,也就是会在内容容器的顶部展示下来。计算marginTop是为了把顶部的渲染列表,对齐外部容器布局。
继续查看 calculateRange()
从代码中可看到,这段函数的核心为计算可视区域(container)中的当前能被渲染的列表,其中包含有的参数:
- offset: 已经“滚动” 过的列表项个数
- from: 渲染列表的第一个下标
- end: 渲染列表的最后一个子项下标
- currentList: 完整的渲染列表
每次通过监听滚动事件,对渲染列表的个数重新计算:
- 计算可见区域列表的个数
getViewCapacity
- 计算偏移个数函数
getOffset
- 计算顶部距离
offsetTop
- 监听 容器大小变更、list数据变更,重新计算
- 外部可以调用一个 scrollTo函数
- 计算内容容器的
height
marginTop
通过对 useVirtualList
的分析,我们可以发现,其实现虚拟滚动思路为:
通过滚动事件,计算滚动的偏移列表个数,计算对应的列表索引,最后计算可视区域的列表项
vue-virtual-scroller
原理
从 useVirtualList
的实现中可以看出其应用的场景是简单的固定高度列表项的情况,而对于一些复杂的动态高度列表项,可以使用 vue-virtual-scroller
来处理
我们来看看 vue-virtual-scroller
是如何处理动态高度列表项的。
基础使用
通过demo使用案例,我们会发现,在使用vue-virtual-scroller
组件时,需要在DynamicScroller
组件中,把 items
传入即可,可想而知item的实际高度是在内部获取到的。
获取动态列表大小
翻看DynamicScroller
组件中,我们会发现,子项的实际高度是通过 ResizeObserver
监听实时更新item的宽度、高度
这里利用
requestAnimationFrame
避免了ResizeObserver
监听的闪烁问题
计算可视区域列表
和 useVirtualList
获取可视区域的个数计算方式大同小异。
vue-virtual-scroller
是同样通过滚动获取获取滚动距离和可视区域起止距离单位。这点区别于useVirtualList
- 根据滚动的距离,计算查找开始索引、结束索引。利用二分查找法查找开始索引
- 开始索引、结束索引 会包含缓冲区的列表子项,故还需获取可视区域的真实起止索引
- 最后,根据可视区域的起止索引,更新可视化列表数据
小结
本文仅通过简要代码去了解虚拟滚动的核心原理。细节边界的处理,默认不展开
通过分析两个常用库的虚拟滚动实现,我们知道虚拟滚动的核心原理为:
- 可视区域的列表数据的计算:可通过滚动距离计算起止索引
- 查找方法有:二分查找法、个数计算法
ResizeObserver
监听列表项的大小变化,更新列表数据的实际大小- 监听滚动事件,更新可视区域的列表数据
基础不牢,地动山摇