miniui虚拟滚动及其部分渲染优化

miniui虚拟滚动及其部分渲染优化

2024年7月1日
1.3千字
5 阅读

前言:虚拟滚动属于长列表渲染的优化方式之一,长列表渲染优化方式还有分页、懒加载、动态缓存数据等。本文对虚拟列表展开描述,首先以一个简单的demo介绍虚拟滚动的核心逻辑,以此展开对miniui的虚拟滚动的代码介绍,再通过浏览器Perfomance工具分析滚动过程并对其渲染过程做出部分逻辑优化。

一、一个简单虚拟滚动demo

虚拟滚动逻辑可以简单理解为三部分组成:

  1. 滚动逻辑注册
  2. 计算可视区
  3. 渲染可视区

  1. 滚动逻辑注册:虚拟滚动是在大数据长列表滚动过程中,只渲染用户可视区部分,因此63行代码需要注册DOM滚动事件
    1. 64行代码:页面初始化加载也属于滚动高度为0的情况,所以需要额外执行
  1. 计算可视区:计算可视区的第一个列表项和最后一个列表项

  1. 渲染可视区:计算新可视区字符串内容,再通过innerHTML嵌入容器

二、miniui虚拟滚动代码执行流程

在miniui的DataGrid组件中,通过设置virtualScroll属性为true,即开启虚拟滚动:

  1. scroll滚动逻辑注册:DataGrid继承基类Control注册scroll事件

  1. _doRemoveRowContent:移除可视区行内容(直接移除table标签)

  1. doUpdateRows:内部执行可视区计算以及可视区渲染
    1. 计算可视区:通过_getViewRegion计算可视区相关渲染信息(起始索引、结束索引、渲染数据源、scroll信息等)

    1. 渲染可视区:通过_createRowsHTML计算出来新可视区的HTML字符串,然后innerHTML整体插入容器当中

  1. 后续逻辑:
    • deferLayout:对当前DataGrid组件doLayout
    • _syncScrollOffset:滚动条的偏移计算(left / top)
    • ……

三、DOM渲染的部分优化

3.1 DOM渲染性能

用perfomance记录该阶段渲染过程

从上图可以看到整个滚动执行逻辑,百分之九十以上的逻辑都是DOM操作(removeChild和innerHTML)

3.2 优化思路

  • removeChild

滚动过程首先执行了_doRemoveRowContent摘除固定列和非固定列下的各自table标签,这个逻辑是重复执行的,因为对于虚拟滚动情况,后续的innerHTML已经存在更新DOM的逻辑,而且中间也不存在相关中断(return)代码逻辑,故这部分代码可以做判断执行。

  • innerHTML

每一次的滚动可视区变化,miniui是将固定列和非固定列其中的两个table进行整体innerHTML赋值,这样对于DOM渲染开销是巨大的,并且如果当可视区的行多点的话(大屏/异性屏),渲染的体验是不好的,所以将其改造为DOM动态更新:

  • 对于结构没有必要改变的DOM只是替换其属性和样式,比如头部和尾部缓冲行;
  • 对于变化的行,进行动态判断是增加还是移除,以此最小单位地进行DOM更新。

如何计算哪些行是需要移除的?哪些行是需要增加的?增加的是头部增加还是尾部增加?

记录上一次可视区的region(可视区计算属性)记为_prevViewRegion,通过进行判断currStartIndex、currEndIndex、prevStartIndex、prevEndIndex四者的关系,得出需要增删的行信息及其增删的位置(头部或者尾部),然后统一通过insertAdjacentHTML传入beforebegin/afterend进行DOM动态渲染。

3.3 代码实现

  • removeChild逻辑优化

  • innerHTML逻辑优化

修改后的doUpdateRows代码:

根据当前可视区信息region和上一次可视区信息_prevViewRegion计算需要新增的行和删除的行:

按需渲染:

对于结构不需要变动的DOM,直接移植:

3.4 优化效果

四、总结

  • 虚拟滚动实际上也属于性能优化中按需加载渲染大方向的优化思路;
  • _createRowsHTML计算当前可视区HTML的字符串理论上可以通过Worker+DocumentFragment来进行计算优化,但实际处理过程中_createRowsHTML调用内部函数太多(this方法、mini方法),不太好抽离。
    • 飞书文档编辑器的渲染是利用虚拟滚动来进行优化的,对于每一次滚动过程中的各个滚动item的状态做了很好的保留,每次的滚动事件触发所耗费时间很短,主线程存在CPP GC垃圾回收释放内存的过程,线程池中也开了多个子线程在工作

文章评论区

欢迎留言交流

未登录,请先注册或登录后发表评论。

Leave comment