另一种渲染大量数据的高效方法:requestAnimationFrame

2,260 阅读2分钟

requestAnimationFrame 是什么

requestAnimationFrame 是一个用于协调动画效果的浏览器 API。它利用浏览器的重绘周期,在每一帧之前调用回调函数,通常用于实现流畅的动画效果。

然而,它也可以用于分帧渲染大量数据,以减少渲染过程对主线程的影响。

问题背景

首先,让我们看看直接插入数据的方法。这是一种简单的方法,它通过循环将大量数据一次性插入到网页中。这种方式存在明显的性能问题,特别是在渲染大量数据时,用户可能会遇到页面卡顿的情况,降低用户体验。

使用原生生成10w数据 vs. 使用 requestAnimationFrame生成10w数据

使用原生生成10w数据

首先,让我们看看直接插入数据的方法。这是一种简单的方法,它通过循环将大量数据一次性插入到网页中。以下是一个示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>直接插入数据</title>
</head>
<body>
  <h2>直接插入数据</h2>
  <button id="directInsertButton">生成数据</button>
  <ul id="direct-insert-list"></ul>

  <script>
    const total = 100000; // 要渲染的总数据量
    const directInsertList = document.getElementById("direct-insert-list");
    const directInsertButton = document.getElementById("directInsertButton");
    const toggleButton = document.getElementById("toggleButton");
    let directInsertMode = true; // 标记当前模式,默认为直接插入模式

    // 直接插入数据
    function directInsertData() {
      for (let i = 0; i < total; i++) {
        const li = document.createElement("li");
        li.innerText = i;
        directInsertList.appendChild(li);
      }
    }

    // 销毁数据
    function destroyData() {
      directInsertList.innerHTML = "";
    }

    // 给生成数据按钮添加点击事件监听器
    directInsertButton.addEventListener("click", function() {
      if (directInsertMode) {
        directInsertButton.innerText='销毁数据'
        directInsertMode=false

        directInsertData();
      }else{
         directInsertButton.innerText='生成数据'

         directInsertMode=true
          destroyData()
      }
    });

  </script>
</body>
</html>

可以发现,当我们点击生成数据时,会发现需要隔一段时间才会显示出来,无疑是大量dom操作导致性能问题。

使用 requestAnimationFrame 进行分帧渲染

requestAnimationFrame 渲染大量数据的基本思路:

  1. 将要渲染的数据分成小批次,每批次包含一定数量的数据。
  2. 使用 requestAnimationFrame 调度每批次数据的渲染。
  3. 在每帧中,渲染一批数据,然后请求下一帧来继续渲染下一批数据。

这种方式能够避免一次性渲染大量数据,减少了对主线程的压力,提高了页面的响应性能。

以下是使用 requestAnimationFrame 渲染大量数据的示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>使用 requestAnimationFrame 渲染数据</title>
</head>
<body>
  <h2>使用 requestAnimationFrame 渲染数据</h2>
  <button id="frameRenderButton">生成数据</button>
  <ul id="frame-render-list"></ul>

  <script>
    const total = 100000; // 要渲染的总数据量
    const batchSize = 1000; // 每帧渲染的数据量
    let currentIndex = 0; // 当前渲染的索引
    const frameRenderList = document.getElementById("frame-render-list");
    const frameRenderButton = document.getElementById("frameRenderButton");
    let frameRenderMode = false; // 标记当前模式,默认为非渲染模式

    // 渲染数据
    function frameRenderData() {
      const fragment = document.createDocumentFragment();
      const endIndex = Math.min(currentIndex + batchSize, total);

      for (; currentIndex < endIndex; currentIndex++) {
        const li = document.createElement("li");
        li.innerText = currentIndex;
        fragment.appendChild(li);
      }

      frameRenderList.appendChild(fragment);

      if (currentIndex < total) {
        requestAnimationFrame(frameRenderData); // 使用 requestAnimationFrame 调度下一帧渲染
      } else {
        frameRenderButton.disabled = false; // 启用按钮
      }
    }

    // 给生成数据按钮添加点击事件监听器
    frameRenderButton.addEventListener("click", function() {
      if (!frameRenderMode) {
        frameRenderButton.innerText = '停止渲染'
        frameRenderMode = true;
        frameRenderButton.disabled = true; // 禁用按钮,避免重复点击
        frameRenderData(); // 开始渲染数据
      } else {
        frameRenderButton.innerText = '生成数据'
        frameRenderMode = false;
        currentIndex = 0; // 重置索引
        frameRenderList.innerHTML = ""; // 清空数据
        frameRenderButton.disabled = false; // 启用按钮
      }
    });

  </script>
</body>
</html>

当我们点击生成数据时,数据会直接生成,当我们滚动滚动条时,会发现数据一直在加载。

结论

使用 requestAnimationFrame 可以有效地渲染大量数据,提高页面的响应性能,避免页面卡顿。这种技术在处理数据可视化、分页加载等场景中非常有用。当你需要在网页中展示大量数据时,不妨考虑使用 requestAnimationFrame 进行性能优化,让用户体验更加流畅。

注意

requestAnimationFrame 虽然是一个简单而有效的解决方案。然而,对于需要处理非常大的数据集合、支持实时搜索和过滤,或者需要无限滚动加载的情况,虚拟列表可能更适合,但它的实现可能更复杂些。