虚拟滚动
type
status
date
slug
summary
tags
category
icon
password
Blocking
Blocked by
top
URL
Sub-item
Parent item
痛点
当表格数据太多时,会渲染出太多的DOM元素(例如100行100列的数据,会渲染出10000个td元素),出现严重的卡顿。
思考过程
想到两种方案:懒加载和虚拟滚动。
- 懒加载:即首次只渲染当前显示节点,当滚动时再插入后面的节点,懒加载的问题在于只能解决首次渲染的问题,后面仍会渲染所有的dom节点。
- 虚拟滚动:即只显示当前可视区域的节点,当滚动时改变动态更新可视区域的节点。好处在于可以复用已渲染的dom节点。
所以最好的方案是采取虚拟滚动
基本思路
通过「单个元素高度」计算当前列表全部加载时的高度作为「滚动容器」的「可滚动高度」,按该「可滚动高度」撑开「滚动容器」。监听滚动容器的滚动事件,根据「当前滚动高度」,在「可视区域」内按需加载列表元素。
固定高度的虚拟列表
根据滚动位置计算出可视元素。再额外渲染上下两个锚点元素,利用intersectionObserver判断,如果锚点元素进入可视范围则再次计算可视元素。
动态高度的虚拟列表
场景
- 列表元素内初始渲染时高度就不确定。比如不定行数的多行文本、列表元素内包含不定长度的内嵌列表等;
- 列表元素内初始渲染后因用户操作而高度发生变化。比如展开一个收缩项目、删除或增加子元素等;
- 列表元素内包含异步渲染元素。比如未缓存过的图片、异步组件等。
解决方法
- 利用ResizeObserver,实时监听每一个处于可视区域内的元素的高度变化,更新元素高度表。存储元素实际高度,如果元素未渲染或者被略过渲染时,用估算高度进行暂时代替。
问题
- 动态高度 多行文本、图片之类的可变内容,会导致列表项的高度并不相同。 解决方法: 以预估高度先行渲染,然后获取真实高度并缓存。
- 白屏闪烁 回调执行也有执行耗时,如果滑动过快会出现白屏/闪烁的情况。为了使页面平滑滚动,我们还需要在可见区域的上方和下方渲染额外的项目,给滚动回调一些缓冲时间。
缺点
- 当前元素的上一个元素高度发生了变化。 这种情况意味着从当前元素开始,每一个后续元素都需要按上一个元素的高度差值进行「scrollY」计算。
- 用户快速拖动滚动条时。 由于略过了中间元素的渲染,
cachedHeight
会缺少略过元素的真实高度,所以只能用上文的ESTIMATED_HEIGHT
进行代替。这种情况下用户再缓慢滚动到顶部时,略过元素的初次渲染会更新cachedHeight
中对应的记录。此时更新的高度肯定是大于或者小于ESTIMATED_HEIGHT
的,所以当用户持续滚动缓慢滚动到scrollTop
为 0 时,可能会出现 上部滚动区域「不足」或者「多余」的情况。因此,必须在保证当前页面滚动情况不变的前提下,提前对这两种情况进行实时修正,也即修正scrollTop
的同时重新计算「锚点元素」。
- 屏幕尺寸变化。当屏幕尺寸变化,可能导致滚动容器的宽高发生变化。可能会影响元素的宽高和可视元素的个数。所以需要重新计算。
问题
无法定位跳转到未渲染元素,因为不知道元素的实际高度
实现过程
- 根据饿了么的再谈前端虚拟列表的实现,实现了虚拟滚动的vue组件,但是存在问题:如果元素的长度不同时,在滚动的过程中仍会增删元素,会影响性能。在我实现虚拟滚动的表格时,因为要实现横纵双向虚拟滚动,dom元素的成倍增加,导致页面会出现明显的卡顿。
- 根据谷歌的无尽滚动的复杂度 -- 来自 Google 大神的拆解,通过DOM回收的机制,最大程度利用已经创建的DOM元素,尽最大可能减少dom的消耗。
参考
infinite-scroll-sample
lkangd • Updated Mar 22, 2024