加载中...

原生js实现虚拟列表_长列表优化

博客 2023.04.21 09:50 1048

问题描述: 成千上万条数据的长列表,用户刷屏次数多,渲染大量dom节点,造成数据加载慢,画面卡顿,影响用户的交互体验,也影响到程序的性能;
解决思路: 一般长列表加载数据采用两种,一种是分页加载,这种大多用于后台管理系统;另一种是无限上下滚动;很明显无限上下滚动会造成以上问题。最主要的就是解决渲染大量dom节点,因此可以采用虚拟列表,只渲染指定的dom节点,只改变数据的内容和滚动的距离;

知识点: css的transform,节流函数;
示例代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="contain">
      <div class="contain-area">
        <div class="list"></div>
      </div>
    </div>
  </body>
</html>

<script>
  // 加载dom节点之后执行loadHtml
  document.onload = loadHtml();
  // 创建10000条数据的数组
  let arr = new Array(10000);
  //计算可视区能显示多少个元素
  const viewCount = 520 / 52;

  let containEle = document.querySelector(".contain");
  // 创建渲染区域的第一个元素的索引startIndex和最后一个元素的索引endIndex
  // 用于截取列表数据的内容
  let startIndex = 0,
    endIndex = viewCount - 1;
  // 获取10个数据的dom节点
  let itemAll = document.querySelectorAll(".list-item");
  //滚动事件
  // 采用节流不会造成白屏
  containEle.onscroll = function () {
    // 获取滚动距离
    let scrollTop = containEle.scrollTop;
    // 获取滚动的元素个数,相当于是第一个元素的索引,向下取整,也可采用Math.ceil
    startIndex = Math.floor(scrollTop / 52);
    // 获取滚动后最后一个元素的索引
    endIndex = 10 + startIndex;
    // 截取列表数据的内容,更新之前10条数据的内容
    let sliceData = arr.slice(startIndex, endIndex);
    for (let index = 0; index < sliceData.length; index++) {
      itemAll[index].innerHTML = startIndex + index + 1;
    }
    // transform移动元素模拟上下滚动加载数据
    document.querySelector(".list").style.transform = `translateY(${
      startIndex * 52
    }px)`;
  };
  // 更新滚动条滚动条的距离,撑高列表的高度,形成滚动的视觉
  document.querySelector(".contain-area").style.height = `${arr.length * 52}px`;
  // 渲染10条数据函数
  function loadHtml() {
    let str = "";
    for (let index = 0; index < 10; index++) {
      str = str + `<div class="list-item">${index + 1}</div>`;
    }
    document.querySelector(".list").innerHTML = str;
  }
  //节流函数 (暂时不用)
  function throttle(fn, delay = 500) {
    let throttleTimer = null;
    let lastTime = 0;
    return function () {
      let nowTime = new Date();
      clearTimeout(throttleTimer);
      if (nowTime - lastTime < delay) {
        fn.apply(this, arguments);
        lastTime = nowTime;
      } else {
        throttleTimer = setTimeout(() => {
          fn.apply(this, arguments);
        }, nowTime - lastTime);
      }
    };
  }
</script>

<style>
  .contain {
    height: 520px;
    overflow-y: scroll;
  }
  .list-item {
    margin: 20px;
    border: solid 1px black;
    text-align: center;
    height: 30px;
    line-height: 30px;
    color: #000;
  }
</style>

效果如下:
动画.gif