美文网首页
【web性能】虚拟列表-渲染大数据量

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

作者: Adder | 来源:发表于2021-06-20 18:23 被阅读0次

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

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

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

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

    1. 当前展示数据项列表
    2. 渲染列表的垂直偏移距离
    3. 更新整个全部数据占位容器的高度
    <template>
      <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
        <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
        <div class="infinite-list" :style="{ transform: getTransform }">
          <div ref="items"
            class="infinite-list-item" 
            v-for="item in visibleData" 
            :key="item.id"
            :style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
          >{{ item.value }}</div>
        </div>
      </div>
    </template>
    
    • 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性能】虚拟列表-渲染大数据量

          本文链接:https://www.haomeiwen.com/subject/enyfyltx.html