虚拟列表
# 虚拟列表
虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。
# 为什么需要虚拟列表
虚拟列表是对长列表的一种优化方案。在前端开发中,会碰到一些不能使用分页方式来加载列表数据的业务形态,我们称这种列表叫做长列表。比如,在一些外汇交易系统中,前端会准实时的展示用户的持仓情况(收益、亏损、手数等),此时对于用户的持仓列表一般是不能分页的。
使用场景:微博的信息流、微信的朋友圈和直播平台的聊天框
# 相关概念
滚动容器元素:意指挂载列表元素的 DOM 对象,它可以是自定义的元素或者 window 对象(默认)。
可滚动区域:滚动容器元素的内部内容区域。假设有 100 条数据,每个列表项的高度是 50,那么可滚动的区域的高度就是 100 * 50
。可滚动区域当前的具体高度值一般可以通过(滚动容器)元素的 scrollHeight 属性获取。用户可以通过滚动来改变列表在可视区域的显示部分。
可视区域:滚动容器元素的视觉可见区域。如果容器元素是 window 对象,可视区域就是浏览器的视口大小(即视觉视口);如果容器元素是某个 div 元素,其高度是 300,右侧有纵向滚动条可以滚动,那么视觉可见的区域就是可视区域。也即是该滚动容器的 offsetHeight。
如何撑开「已浏览区域」和「待浏览区域」,有两种常规的做法:
直接使用 padding 撑开列表高度;
在列表可视区域外部放置哨兵元素撑开高度。
(1)第一种用 padding 撑开列表实现步骤
从上图可以看出,startOffset 和 endOffset 会撑开容器元素的内容高度,让其可持续的滚动;此外,还能保持滚动条处于一个正确的位置。
实现虚拟列表就是在处理用户滚动时,要改变列表在可视区域的渲染部分,其具体步骤如下:
- 计算当前可见区域起始数据的 startIndex
- 计算当前可见区域结束数据的 endIndex
- 计算当前可见区域的数据,并渲染到页面中
- 计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上
- 计算 endIndex 对应的数据相对于可滚动区域最底部的偏移位置 endOffset,并设置到列表上
(2)哨兵元素撑开高度
实现步骤:
- 根据单个元素高度计算出滚动容器的可滚动高度,并撑开滚动容器;
- 根据可视区域计算总挂载元素数量;
- 根据可视区域和总挂载元素数量计算头挂载元素(初始为 0)和尾挂载元素;
- 当发生滚动时,根据滚动差值和滚动方向,重新计算头挂载元素和尾挂载元素。
# 定高虚拟列表实现
因此,实现「虚拟列表」可以简单理解为就是在列表发生滚动时,改变「可视区域」内的渲染元素。大概的文字逻辑步骤如下:
# 不定高虚拟列表实现
两种方案:
- 传入一个 estimateHeight 属性先对行高进行估计并渲染,然后渲染完成后获得真实行高并进行更新和缓存
会引入多余的 transform(可以接受)
- 将当前元素先在屏外进行绘制并对齐高度进行测量后再将其渲染到用户可视区域内
这种方法相当于双倍渲染消耗(不切实际)
# 长列表
前端的业务开发中会遇到一些数据量较大且无法使用分页方式来加载的列表,我们一般把这种列表叫做长列表
完整渲染的长列表基本上很难达到业务上的要求的,非完整渲染的长列表一般有两种方式:
懒渲染:这个就是常见的无线滚动的,每次只渲染一部分(比如 10 条),等剩余部分滚到可见区域,就再渲染一部分。
可视区域渲染:只渲染可见部分,不可见部分不渲染。
参考:如何实现一个高度自适应的虚拟列表 (opens new window) 「前端进阶」高性能渲染十万条数据(虚拟列表) (opens new window) 浅说虚拟列表的实现原理 (opens new window) 剖析无限滚动虚拟列表的实现原理 (opens new window)