高性能渲染十万条数据:时间分片、虚拟列表、懒加载

高性能渲染大量数据

对于大量数据的渲染,可以采用时间分片和虚拟列表的处理方式

时间分片

简单点理解就是把数据拆成很多份,分批地来进行渲染。页面卡顿是因为同时渲染大量 DOM 所引起的可使用时间分片。

方式一setTimeout
缺点:但是当我们快速滚动页面的时候,会发现页面出现闪屏或白屏的现象

//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
    
    
    if(curTotal <= 0){
    
    
        return false;
    }
    //每页多少条
    let pageCount = Math.min(curTotal , once);
    setTimeout(()=>{
    
    
        for(let i = 0; i < pageCount; i++){
    
    
            let li = document.createElement('li');
            li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
            ul.appendChild(li)
        }
        loop(curTotal - pageCount,curIndex + pageCount)
    },0)
}
loop(total,index);

方式二requestAnimationFrame + DocumentFragment

//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
    
    
    if(curTotal <= 0){
    
    
        return false;
    }
    //每页多少条
    let pageCount = Math.min(curTotal , once);
    window.requestAnimationFrame(function(){
    
    
        let fragment = document.createDocumentFragment();
        for(let i = 0; i < pageCount; i++){
    
    
            let li = document.createElement('li');
            li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
            fragment.appendChild(li)
        }
        ul.appendChild(fragment)
        loop(curTotal - pageCount,curIndex + pageCount)
    })
}
loop(total,index);

虚拟列表

虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除。

1、计算当前可视区域起始数据索引(startIndex)
2、计算当前可视区域结束数据索引(endIndex)
3、计算当前可视区域的数据,并渲染到页面中
4、计算startIndex对应的数据在整个列表中的偏移位置startOffset并设置到列表上
在这里插入图片描述

<!-- 虚拟列表 -->
  <div class="box">
    <!-- 内容真实高度 即200条数据的高度-->
    <div class="content-area" id="contentArea">
      <ul></ul>
    </div>
  </div>
	let el = document.querySelector(".box");
    let itemHeight = 110; //每个元素的高度(li的高度100 + marginBottom 10)
    let pageSize = Math.ceil(el.clientHeight / itemHeight); // 获取一个滚动屏最大可容纳子元素个数(向上取整)
    let data = []; //mock数据
    let startIndex = 0; //可视区第一行下标
    let endIndex = pageSize; //可视区最后一行下标

    // 初始化模拟数据
    let getData = () => {
    
    
      for (let i = 0; i < 200; i++) {
    
    
        data.push({
    
    
          content: `我是显示的内容${
      
      i + 1}`,
        });
      }
    };

    // 加载数据并插入到dom页面
    let loadData = () => {
    
    
      let html = "";
      let sliceData = data.slice(startIndex, endIndex);
      for (let i = 0; i < sliceData.length; i++) {
    
    
        html += `
            <li class="item">
              <p>${
      
      sliceData[i].content}</p>
            </li>`;
      }
      el.querySelector("#contentArea ul").innerHTML = html;
    };

    // 更新DOM
    let updateHtml = () => {
    
    
      let sliceData = data.slice(startIndex, endIndex);
      let itemAll = el.querySelectorAll(".item");
      for (let i = 0; i < sliceData.length; i++) {
    
    
        itemAll[i].querySelector("p").innerHTML = sliceData[i].content;
      }
    };

    // 滑动监听
    el.addEventListener("scroll", function () {
    
    
      let scrollTop = el.scrollTop; // 滚动高度
      startIndex = Math.ceil(scrollTop / itemHeight); // 重新计算开始的下标,div顶部卷起来的长度除以列表元素的高度
      endIndex = startIndex + pageSize;
      updateHtml(); // 重新更新dom
      el.querySelector("#contentArea ul").style.transform =
        "translateY(" + startIndex * itemHeight + "px)";
    });

    let init = () => {
    
    
      getData();
      loadData();
      document.getElementById("contentArea").style.height =
        itemHeight * data.length + "px"; // 占位dom的高度
    };

    // 页面初始化调用
    init();

懒加载

不多介绍,一句话解释:最开始不渲染所有数据,只展示视图上可见的数据,当滚动到页面底部时,加载更多数据

实现原理:通过监听父级元素的 scroll 事件,当然也可以通过 IntersectionObserver 或 getBoundingClientRect 等 API 实现

但 scroll 事件会频繁触发,所以需要手写节流;滚动元素内有大量 DOM ,容易造成卡顿,建议使用 IntersectionObserver

猜你喜欢

转载自blog.csdn.net/qq_38110274/article/details/127527148