1. 需要的 DOM 结构
    1. 父容器
    2. 占位高度
    3. 实际内容区域
  2. 初始化
    1. startIndex = 0
    2. 当前加载个数 = Math.ceil(可视区域高度 / 每列高度)
    3. endIndex = startIndex + count
    4. 占位高度 = 数据总个数 * 每列高度
    5. 赋值 listDatalist.slice(startIndex, endIndex)
  3. 监听父容器 onscroll 事件
    1. 计算 startIndex = Math.floor(滚动条距离 / 每列高度)
    2. endIndex = startIndex + count
    3. 重新赋值 listData
    4. 抵消滚动发生的偏移:transform: transform3d(0, startIndex * 每列高度)
<!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>
    <style>
      * {
        padding: 0;
        margin: 0;
      }
      .list-view {
        height: 400px;
        position: relative;
        overflow-y: auto;
      }

      .zhanwei {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: -1;
      }

      .list-content li {
        height: 100px;
      }

      /* .list-content li:nth-of-type(odd) {
        background: #00ccff;
      }

      .list-content li:nth-of-type(even) {
        background: #ffcc00;
      } */
      .list-content li {
        background: #ffcc00;
      }
    </style>
  </head>
  <body>
    <!-- 可视区 -->
    <div id="listView" class="list-view">
      <!-- 占位高度 -->
      <div id="zhanwei" class="zhanwei"></div>
      <!-- 实际列表 -->
      <ul id="listContent" class="list-content"></ul>
    </div>
    <script>
      let temp = 3;
      // 模拟常列表的数据
      function setLongData() {
        let data = [];
        for (let i = 0; i < 1000000; i++) {
          data.push({ id: "item" + i });
        }
        return data;
      }
      function selectDom(selector) {
        return document.querySelector(selector);
      }
      // 加载数据并插入DOM到页面
      function loadData(start, end) {
        // 截取数据
        let sliceData = setLongData().slice(start, end);
        // 创建虚拟DOM
        let F = document.createDocumentFragment();
        for (let i = 0; i < sliceData.length; i++) {
          let li = document.createElement("li");
          li.innerText = JSON.stringify(sliceData[i]);
          li.className = sliceData[i].id;
          F.appendChild(li);
        }
        selectDom(".list-content").innerHTML = "";
        selectDom(".list-content").appendChild(F);
      }
      // 设置占位DOM的高度
      document.getElementById("zhanwei").style.height = `${
        100 * setLongData().length
      }px`;
      // 计算可视区能展示几列数据,此处假设一列的高度为 100 px
      let count = Math.ceil(document.body.clientHeight / 100);
      console.log(document.body.clientHeight);
      let startIndex = 0; // 可视区第一列的索引
      let endIndex = count; // 可视区最后一列的索引
      loadData(startIndex, endIndex);

      // 滚动加载数据方法
      function scrollFunction() {
        // 获取滚动条距离可视区顶部的距离
        let scrollTop = document.getElementById("listView").scrollTop;
        startIndex = Math.floor(scrollTop / 100);
        endIndex = startIndex + count;
        loadData(startIndex, endIndex);
        // 滚动时内容区会发生偏移
        // 通过 transform:translate3d将偏移的内容移回可视区
        document.getElementById(
          "listContent"
        ).style.transform = `translate3d(0, ${startIndex * 100}px, 0)`;
      }
      document
        .getElementById("listView")
        .addEventListener("scroll", scrollFunction);
    </script>
  </body>
</html>