1、为什么会有虚拟列表?

        在前端开发中,有时候会出现一些不能使用分页来加载数据的情况,因此当我们需要渲染上万条数据的时候,就可能会出现卡顿的情况,这个时候我们就会提出虚拟列表的概念。

2、什么是虚拟列表

     虚拟列表就是按需显示的一种实现,即只对可见区域进行渲染,对不可见区域中的数据不渲染或者部分渲染的技术,从而达到极高的渲染性能。假设有1千条数据需要同时渲染,屏幕可见区域的高度是800px,每一个列表的高度是100px,则我们在屏幕中就最多能看到8个列表项。那么在渲染的时候,我们只需要加载可视区域中的那8条数据即可(即只渲染8个dom元素)。

3、基本思路

  1. 写一个代表可视区域的div容器
  2. 计算可视区域可以显示的数据条数(可视区域的高度/单条数据的高度)
  3. 监听滚动,当滚动条变化时,计算被卷起的高度(scrollTop)
  4. 计算可视区域的起始下标(卷起的高度/单条数据的高度)
  5. 计算可视区域的结束下标(起始下标+可视区域可以显示的数据条数)
  6. 取起始下标到结束下标之间的数据,渲染到页面
  7. 计算起始下标的数据在整个列表中的偏移位置并设置到列表上(transform:translateY())

4、效果图

element虚拟列表的原理 js虚拟列表_javascript

5、代码 

<!DOCTYPE html>
<html>
  <head>
    <title>js 实现虚拟列表</title>
  </head>
  <style>
    html,
    body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }

    ul,
    li {
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .box {
      width: 750px;
      height: 900px;
      overflow-y: auto;
      box-sizing: border-box;
      margin: 0 auto;
    }

    .content-area {
      overflow: hidden;
    }

    li {
      height: 100px;
      background-color: #eee;
      margin-bottom: 10px;
      display: flex;
      align-items: center;
      padding-left: 20px;
      border-radius: 12px;
      box-sizing: border-box;
    }
  </style>
  <body>
    <!-- 可视区域的高度--用户看见的高度(750px) -->
    <div class="box">
      <!-- 内容真实高度 即200条数据的高度-->
      <div class="content-area" id="contentArea">
        <ul></ul>
      </div>
    </div>
    <script>
      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();
    </script>
  </body>
</html>