【web性能】虚拟列表-渲染大数据量

当我们遇到需要一次性向页面插入十万条数据的情况下,该如何保证页面不卡顿,维持一定的页面渲染性能

场景:
插入十万条数据,渲染到页面十万条数据
分析:
我们知道,UI渲染在浏览器渲染进程中属于宏任务,且涉及到页面的绘制,因此执行完当前的的脚本,进入宏任务阶段后,同时由于数据量大,整个渲染耗费时间较长
方案:

  1. 把数据分批插入到页面
  2. 虚拟列表,渲染应该渲染的 ✅(本篇内容本文基于vue实例做介绍

实际上思路就是,在首屏渲染的时候,只加载可视区域内的列表项,当发生滚动的时候,动态计算:

  1. 当前展示数据项列表
  2. 渲染列表的垂直偏移距离
  3. 更新整个全部数据占位容器的高度

  • infinite-list-container: 可视区域
  • infinite-list-phantom:占位容器(生成滚动条)
  • infinite-list:渲染区域,通过设置垂直偏移量,模拟滚动效果

1️⃣ 等高的数据项


数据源model相关
  • itemSize:数据项高度
  • startIndex:可渲染区域的,起始索引值
  • endIndex:可渲染区域的,结束索引值(起始 + 可渲染数据项visibleCount)
  • visibleData:可渲染区域的,列表数据
  • screenHeight: 可是区域高度
  • startOffsetY:可渲染区域的垂直偏移距离
更新视图view相关

监听container 可视区域的滚动事件scroll时,拿到最新的scrollTop值,可以计算更新相关值:

  • 起始索引startIndex = Math.ceil(scrollTop / itemSize)
  • 可渲染数量visibleCount = Math.ceil(screenHeight / itemSize)
  • 结束索引endIndex = startIndex + visibleCount
  • 可渲染数据visibleData = listData.slice(startIndex, endIndex)
  • 渲染区域偏移距离startOffsetY = scrollTop - (scrollTop % itemSize)

查看demo(vue)实现效果

然而在具体实现的时候,很多列表的各数据项,是不定高度的;因此我们考虑实现动态计算高度

2️⃣ 动态高度的数据项


  • 默认数据项高度estimatedItemSize 用于初始化
  • 记录数据项位置和高度positions:
this.positions = this.listData.map((d, index) => ({
        index,
        height: this.estimatedItemSize,
        top: index * this.estimatedItemSize,
        bottom: (index + 1) * this.estimatedItemSize
}));
更新view视图

监听container 可视区域的滚动事件scroll时,拿到最新的scrollTop值,可以计算更新相关值:

  • 起始索引(动态计算):this.positions.findIndex(item => item.bottom > scrollTop)
  • 可渲染数量visibleCount : Math.ceil(screenHeight/ estimatedItemSize)
  • 结束索引endIndex : startIndex + visibleCount
  • 可渲染数据visibleData : listData.slice(startIndex, endIndex)
  • 渲染区域偏移距离startOffsetYthis.positions.find(item => item.bottom > scrollTop)这个起始项top
更新model

每次渲染完成之后,在vue 的 updated钩子($nextTick)里面处理更新和校对操作

  1. 校对positions: 通过拿到页面dom数据项,拿到节点高度,遍历数组与缓存的positions做对比:不同则需要更新该节点的position值(同时,需要更新后续节点的值,因为后一项的top,其实也是前一项的bottom
  2. 校对滚动条长度: 更新占位容器phantom-list的实际高度:positions最后一项的bottom
  3. 校对渲染区域位置更新渲染区域content-list的垂直偏移量startOffsetY

然而从最终效果看,在滑动速度较快的情况下,仍然会出现空屏的情况...
因此,我们考虑加上前后两层缓冲区,前后分别都添加上缓冲区数据,计算visibleData时,

// AVGSCALE 比如是 0.5
const startIndex = start - (visibleCount * AVGSCALE);
const endIndex = end + (visibleCount * AVGSCALE);
return this.listData.slice(startIndex, endIndex)

查看动态高度demo实现效果

思考

目前的更新操作,是放在scroll监听事件中处理,这种高频触发方案,难免会重复计算损耗性能,可以考虑在intersectionObserver这里监听,在回调方法里面处理相关的更新操作

这两种方案目前是借助js操作的角度,去优化大数据量渲染的性能问题;在css层面,也有相关的优化方案,其中content-visibility属性,就是一个很有效的属性(但是它的兼容不好)

参考

高性能渲染十万条数据-虚拟列表

你可能感兴趣的:(【web性能】虚拟列表-渲染大数据量)