性能优化之长列表渲染——时间分片和虚拟列表

为什么要做长列表优化,让我们看一段代码的执行。
  let now = Date.now();// 记录任务开始时间
  let ul = document.getElementById('container'); //获取容器
  let total = 100000;
  for(let i = 0; i< total;i++) {
    let li = document.createElement('li');
    li.innerText = i;
    ul.appendChild(li)
  }
  console.log('JS运行时间:',Date.now() - now);
  setTimeout(()=>{
    console.log('总运行时间:',Date.now() - now);
  },0)

在这里插入图片描述
可以看出当对十万条数据进行操作时,js运行时间166ms,但是最后渲染完成之后的时间确实4511ms.
图中可以看出:话费时间主要在:
Recalculate StyleLayout阶段,即样式计算和布局。
所以为了解决渲染阶段的性能瓶颈,我们主要通过减少渲染的时间。

第一种解决方案:采用时间分片。

1.setTimout处理。

  let now = Date.now();// 记录任务开始时间
  let ul = document.getElementById('container'); //获取容器
  let total = 100000;
  let pageSize = 30;
  let currentPage = 1;
  let endPage = Math.ceil(total/pageSize);
  let currentTotal = total-pageSize*currentPage 
  function load(){
    if(currentPage > endPage) {
      return false;
    }
    let currentCount = Math.min(currentTotal, pageSize)
    setTimeout(() => {
      for(let i = 1;i<=currentCount;i++) {
        let li = document.createElement('li');
        li.innerText = currentPage + ':' + i;
        ul.appendChild(li)
      }
      currentPage++;
      currentTotal = total-pageSize*currentPage 
      load()
    },0)
  }
  load()

但是,这种情况,在快速滑动情况下会导致白屏或者闪屏。原因就是关于js的执行进程和渲染进程的互斥。

关于setTimout和闪屏?
具体就是,浏览器内核的渲染进程:宏任务-渲染-宏任务-渲染这样…
1.因为setTimout的执行时间并不确定。在js执行机制中,setTimeout任务被放进事件队列中。只有主线程执行完毕才会检查事件队列中的任务。
2.刷新频率会受屏幕分辨率和屏幕尺寸的影响。
以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致。
解决setTimout带来的问题,可以引入requestAnimationFrame

2.使用 requestAnimationFrame
requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象。

	//把上面的setTimout换掉
    window.requestAnimationFrame(function(){
      for(let i = 1;i<=currentCount;i++) {
        let li = document.createElement('li');
        li.innerText = currentPage + ':' + i;
        ul.appendChild(li)
      }
      currentPage++;
      currentTotal = total-pageSize*currentPage 
      load()
    })

关于虚拟列表的实现

在这里插入图片描述
其是虚拟列表的主要特点就是减少dom数量,只需要改编list里每一项Item的值就好(即图中infinite-list-item的值)。这样加快dom树的构建。

  <div id="infinite-list-container">
    <!-- 用于占位 -->
    <div id="infinite-list-phantom"></div>
    <!-- 列表插入 -->
    <div id="infinite-list">

    </div>
  </div>

<script>
  (function () {
    let total = 100;
    let allData = [] //所有数据
    for (let i = 0; i < total; i++) {
      allData.push(i)
    }
    let $list = document.getElementById('infinite-list-container')
    let $listPhantom = document.getElementById('infinite-list-phantom')
    let $listContent = document.getElementById('infinite-list')
    let itemSize = 100 //每一项的高度
    let listHeiht = itemSize * total; //列表的总高度
    let screenHeight = $list.clientHeight; //可见区域的高度
    let visibleCount = Math.ceil(screenHeight / itemSize); //可见区域的列表项数
    let start = 0; //起始位置
    let end = start + visibleCount; //结束位置
    let visivleData = allData.slice(start, Math.min(end, allData.length)) //可见区域的数据
    let startOffset = 0 //偏移量
    $listPhantom.style.height = allData.length * itemSize + 'px'
    for (let i = 0; i < visivleData.length; i++) {
      let div = document.createElement('div');
      div.innerText = visivleData[i];
      div.style.height = itemSize + 'px'
      div.setAttribute('class', 'infinite-list-item')
      $listContent.appendChild(div)
    }
    $list.addEventListener('scroll', () => {
      console.log('sss')
      let scrollTop = $list.scrollTop || document.documentElement.scrollTop;
      console.log(scrollTop)
      //此时的开始索引
      start = Math.floor(scrollTop / itemSize);
      //结束索引
      end = start + visibleCount;
      //偏移量
      startOffset = scrollTop - scrollTop % itemSize
      $listContent.style = `transform: translate3d(0,${start * itemSize}px,0)`
      changeData();
    })
    function changeData() {
      visivleData = allData.slice(start, Math.min(end, allData.length))
      console.log(visivleData)
      for (let i = 0; i < visivleData.length; i++) {
        let div = document.getElementsByClassName('infinite-list-item')[i];
        div.innerText = visivleData[i];
        div.style.height = itemSize + 'px'
      }
    }
  })()
  </script>
  <style>
    #infinite-list-container {
      height: 100%;
      overflow: auto;
      position: relative;
      -webkit-overflow-scrolling: touch;
    }

    #infinite-list-phantom {
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      z-index: -1;
    }

    #infinite-list {
      left: 0;
      right: 0;
      top: 0;
      position: absolute;
      text-align: center;
    }

    .infinite-list-item {
      padding: 10px;
      color: #555;
      box-sizing: border-box;
      border-bottom: 1px solid #999;
    }
  </style>
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值