### 需求场景
刷不到底的信息流,无尽图片,复杂的DOM结构…
总之,有长列表,有复杂元素,就可能需要优化
对于很多场景,我们不能一次性加载完所有的数据并且全都渲染出来,这既耗费性能,又影响用户体验。
这里介绍几种常见的解决方法,以及它们的特点。
分页加载
- 需要后端支持
- 分页请求可能会打断用户体验
- 有筛选项时(筛选部分列表项展示)可能触发新的请求,造成延迟
懒加载
- 解决首屏加载问题
- 并没有解决性能问题
虚拟列表
- 实现较麻烦
- 长时间加载大量数据不会有性能负担
- 更新时可能会出现跳变
- 滑动过快可能会出现白屏
综合来说,虚拟列表在用户体验和应用性能方面都表现的比较好,但处理不当可能会出现跳变、白屏的问题。
因此在实际情况中是否需要使用,还是需要综合考虑数据量、是否支持请求分页、每个列表项dom元素的复杂程度、是否有筛选项等因素,再去权衡。
原理分析
什么是虚拟列表呢?
虚拟列表是一种根据滚动容器元素的可视区有选择地渲染长列表中某一部分数据的方案
首先,我们要有一个很长很长很长的列表,还要有每个列表项对应的dom素。
我们知道,对于整个列表,如果要将所有元素渲染到屏幕上,数据如果多了,就容易出现卡顿。
因此,我们需要对列表和视窗之间进行一些划分。
简单实现
接下来我们一起实现一个精简的版本,大概思路是:
请求所有数据,对列表分页,监听滚动,计算当前应该展示到第几页,然后截取对应区间的数据,更新展示区域。
具体实现时,现假设我们的列表项是固定长度的,并且能够计算得到。
变量
定义以下变量:
1 | totalList: [], // 总列表 renderList: [], // 渲染列表 curPage: 1, // 当前展示的页索引,从1开始 containerTop: 0 // 渲染列表盒子在整个列表盒子的高度 // constants perPageNum: 10, // 每页包含列表项数 itemHeight: 100, // 每个列表项的高度 showLen: 3 // 缓冲渲染长度 |
关于参数的设置
我们对整个列表进行了分页,一页包含的列表项的总高度大约是一个视窗的高度,建议设置得比实际数量大一些,便于缓冲
根据itemHeight和curPage记录当前视窗显示的第一个元素所属的页,也就是当前要展示的页
showLen为缓冲,这里一般设置为3,表示渲染当前视窗及其上下共3个页面,满足基本一次手势上划和下拉的展示缓冲
页面结构
来两个盒子:
1 | .scroll-box(:style="") .render-box(:style="") |
外层scroll-box用来放整个长列表
列表项只起到占高度的作用,并没有实际元素,因此不会造成页面负担
如不考虑过滤功能,高度totalHeight直接设置为totalList.length * itemHeight
相对定位
内层render-box存放真实渲染的列表项
绝对定位,相对于父元素偏移
可以直接通过改top属性保证它在视窗内(可以优化)
列表划分
列表划分的关系如下图:
##### 计算
计算页值
计算红色部分视窗对应的页索引curPage
实现:在长列表盒子中监听滚动事件,动态计算curPage的值,注意这里curPage是从1开始计数:
1 | handleScroll (e) |
现在我们已经得到了正确的页面值啦,写的时候可以先在浏览器里面上下滑动试试
计算渲染列表及高度
接下来要做的,就是在每次更新curPage时,渲染它及其上下的页面对应的列表项,同时改变渲染部分盒子的高度,使其位于视窗中
计算也很简单,需要考虑以下两种情况:
1. 首次滚动
- 前三个页面的加载containerTop仍然为0
- 渲染列表截取0到3页内容
- 持续滚动
- containerTop高度为视窗上方的高度,即(curPage - 1) * perPageNum * itemHeight
- renderList修改为curPage对应页及其上下两页的内容。例如图2中curPage从3到4,那我们需要截取的就是第3、4、5页的列表项,这里slice用的是索引值所以要减一到这里,一个简单的虚拟列表就实现好了,代码也非常少
1
update () else }
补充完善
当然,我们还需要补充一下
筛选功能(如果有)
在每次totalList使用前过滤一层再用即可
平滑移动
直接改containerTop容易出现跳变,当翻页时可能会突然偏移,因此可以使用transform修改样式
缓存
这里因为数据量不大(没有上千),性能问题主要出在dom结构复杂,因此请求了全部数据,只做了缓冲,并没有进行缓存
要写缓存也可以用哈希表然后二分查找
列表元素变长
这就需要去动态获取元素的高度了,当然可能会遇到兼容性的问题,计算的方法并没有多少变化
性能
可以查看浏览器分析计算、渲染等花费的时间来分析性能
小程序可以开启数据更新打印,便于定位性能瓶颈
最后
以上只是一种比较精简的虚拟列表的实现,不同的方案主要在缓冲设置上的区别比较大,其他的主要是变量设置不同,例如可以维护渲染列表的首尾索引而不是整个渲染列表,不使用多页缓冲进行缓存等
不同版本的实现,可以去读一些较官方版本的源码,如:
react-tiny-virtual-list,react-virtualized, vue virtual scroll list