前端性能救星!用 requestAnimationFrame 丝滑渲染海量数据

2,698 阅读4分钟

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个做了6年前端的老司机。今天想和大家聊聊一个常见但头疼的问题:如何在不卡死页面的情况下渲染大量数据?

不知道你有没有遇到过这样的场景:后端一次性返回几万条数据,前端直接 forEach + appendChild 暴力渲染,结果页面直接卡死,用户疯狂投诉……(别问我怎么知道的 😅)

其实,解决这个问题并不难,关键在于分批渲染,而 requestAnimationFrame(简称 rAF)就是我们的最佳帮手!


1. 为什么直接渲染大数据会卡?

假设我们有 10,000 条数据,如果一次性全部塞进 DOM:

const data = Array(10000).fill("我是数据项");  
const container = document.getElementById('list');  

data.forEach(item => {
  const div = document.createElement('div');
  div.textContent = item;
  container.appendChild(div);
});

问题来了:

  • JS 执行阻塞:一次性创建 10,000 个 DOM 节点,JS 引擎会卡住主线程。
  • 渲染延迟:浏览器需要处理大量 DOM 插入,导致页面冻结,用户无法交互。
  • 内存占用高:大量 DOM 节点堆积,内存飙升,甚至可能崩溃。

结论:  不能一次性渲染所有数据!


2. 解决方案:分批渲染 + requestAnimationFrame

requestAnimationFrame 是浏览器提供的 API,它会在下一次重绘之前执行回调,通常是 16.6ms(60FPS)  执行一次,这样可以让渲染更流畅。

优化思路:

  1. 分批次渲染:每次只渲染一小部分数据(比如 50 条)。
  2. 利用 rAF 控制渲染时机,避免阻塞主线程。
  3. 滚动加载优化(可选):结合 IntersectionObserver 实现懒加载。

代码实现:

function renderLargeData(data, chunkSize = 50) {
  const container = document.getElementById('list');
  let index = 0;

  function renderChunk() {
    // 每次渲染 chunkSize 条数据
    const chunkEnd = Math.min(index + chunkSize, data.length);
    
    for (; index < chunkEnd; index++) {
      const div = document.createElement('div');
      div.textContent = 我是.data[index];
      container.appendChild(div);
    }

    // 如果还有数据,继续渲染
    if (index < data.length) {
      requestAnimationFrame(renderChunk);
    }
  }

  // 开始渲染
  renderChunk();
}

// 测试
const bigData = Array(10000).fill("我是数据项");
renderLargeData(bigData);

优化效果:
✅ 页面不卡顿:每次只渲染少量 DOM,主线程不会被阻塞。
✅ 流畅渲染rAF 确保在浏览器空闲时执行,不影响用户交互。
✅ 内存可控:不会一次性创建大量 DOM,减少内存压力。


3. 进阶优化:虚拟滚动(Virtual Scroll)

如果你的数据量真的超级大(比如 10W+),即使分批渲染也会占用大量 DOM,这时可以考虑 虚拟滚动(只渲染可视区域内的元素)。

核心思路:

  • 只渲染可见区域的数据,滚动时动态替换内容。
  • 使用 IntersectionObserver 或监听 scroll 事件计算可视范围。
// 简单示例(完整实现会更复杂)
function renderVirtualScroll(data, container, itemHeight = 50) {
  const viewportHeight = container.clientHeight;
  const visibleItemCount = Math.ceil(viewportHeight / itemHeight);
  let startIndex = 0;

  function updateVisibleItems() {
    const endIndex = startIndex + visibleItemCount;
    const visibleData = data.slice(startIndex, endIndex);

    container.innerHTML = '';
    visibleData.forEach(item => {
      const div = document.createElement('div');
      div.style.height = `${itemHeight}px`;
      div.textContent = 我是.item;
      container.appendChild(div);
    });
  }

  container.addEventListener('scroll', () => {
    startIndex = Math.floor(container.scrollTop / itemHeight);
    updateVisibleItems();
  });

  updateVisibleItems();
}

适用场景:
📌 超长列表(如聊天记录、日志数据)
📌 表格数据渲染
📌 无限滚动(Infinite Scroll)


4. 总结:如何选择优化方案?

方案适用场景优点缺点
分批渲染(rAF数据量中等(1k~10k)实现简单,兼容性好DOM 节点仍较多
虚拟滚动数据量超大(10k+)极致性能,内存占用低实现较复杂

我的建议:

  • 如果数据量  < 5000,直接渲染问题不大。
  • 如果数据量 5000~10W,用 requestAnimationFrame 分批渲染。
  • 如果数据量  > 10W,考虑虚拟滚动或后端分页。

5. 最后的小技巧

  • setTimeout vs rAFrAF 比 setTimeout 更适合动画/渲染,因为它与浏览器刷新率同步。
  • Web Worker:如果数据处理很耗时(如排序、过滤),可以丢给 Web Worker,避免阻塞 UI。
  • 分页加载:如果可能,尽量让后端分页,减少前端压力。

结语

渲染大数据并不可怕,关键是要避免阻塞主线程requestAnimationFrame 是一个强大的工具,合理使用能让你的页面丝滑流畅!