渲染

一次性渲染大量数据不卡顿

原理: 时间换空间,使用 requestIdleCallback 或者 requestAnimationFrame 调度渲染。 缺点: 大量dom需要占用较多内存

<body>
  <ul id="list"></ul>

  <script>
    function render() {
      const FRAME_TIME = 1000 / 60; // 定义每帧的时长
      const TOTAL_ITEMS = 100000; // 总条数
      const ITEMS_PER_RENDER = 20; // 每次渲染20条
      const RENDER_COUNT = TOTAL_ITEMS / ITEMS_PER_RENDER;
      const list = document.getElementById('list');
      let renderedCount = 0;

      function renderItem(deadline) {
        if (deadline ? deadline.timeRemaining() > 0 : true) {
          // 1. 使用文档碎片优化一次插入优化性能
          const fragment = document.createDocumentFragment();
          for (let i = 0; i < ITEMS_PER_RENDER; i++) {
            const item = document.createElement('li');
            item.innerText = Math.floor(Math.random() * TOTAL_ITEMS);
            fragment.appendChild(item);
          }
          list.appendChild(fragment);
          renderedCount += 1;
        }
        loop();
      }

      function loop() {
        //  使用 requestIdleCallback 或者 requestAnimationFrame 函数循环渲染 li 元素,避免卡顿
        if (renderedCount < RENDER_COUNT) {
          // 2. 兼容性处理,使用 requestIdleCallback 或 requestAnimationFrame
          if ('requestIdleCallback' in window) {
            window.requestIdleCallback(renderItem);
          } else {
            scheduleAnimationTask(renderItem);
          }
        }
      }

      function run() {
        if ('requestIdleCallback' in window) {
          window.requestIdleCallback(renderItem);
        } else {
          scheduleAnimationTask(renderItem);
        }
      }
      function scheduleAnimationTask(callback) {
        let startTime = Date.now();
        requestAnimationFrame(() => {
          if (Date.now() - startTime < FRAME_TIME) {
            callback();
          } else {
            scheduleAnimationTask(callback);
          }
        });
      }
      run();
    }

    render()

  </script>
</body>

虚拟列表实现

原理:按需渲染, 只渲染视口范围内可见的元素。 步骤:

  • 初始化:每一项高度预估一个默认值,用一个元素如这里的scrollHold元素撑开滚动条高度。

  • 根据滚动高度确定视口元素的起始位置start,计算视口的区间元素位置positions[start,end]

  • 根据start计算滚动内容容器的偏移量currentOffset利用css的transform设置滚动内容容器的偏移量

  • 渲染后计算实际内容高度记录下来,更新虚拟滚动条内容高度。

TODO:

  • 使用骨架屏作为缓冲区。

  • 算法上的优化缓存高度等。

  • 滚动获取数据,请求过程中显示骨架屏。数据渲染显示数据隐藏骨架屏。

Last updated