在前端开发中,当我们需要展示海量数据(比如 10 万条、100 万条列表项)时,直接将所有内容渲染到 DOM 中会导致性能瓶颈。即使使用 `requestIdleCallback` 分批渲染,也依然无法解决 内存占用大滚动迟钝 的问题。

本篇文章将介绍如何使用「虚拟滚动(Virtual Scrolling / Windowing)」技术,在用户滚动过程中只渲染可视区域的数据,从根本上解决大数据列表的性能问题。我们会讲解虚拟滚动的原理、实现步骤、优化点,并提供完整的示例代码。


🌟 什么是 Virtual Scrolling?

虚拟滚动是一种只渲染「可视区域」数据项的优化技术。在一个大型列表中,实际上用户每次只看到几十条数据,其余内容并不需要出现在 DOM 中。

✅ 核心原理:

  • 固定容器高度 + 固定单项高度
  • 通过计算可视区域中的起始索引和结束索引
  • 只渲染视口中可见的那些元素
  • 上下使用「占位容器」撑起整体滚动高度
真实列表(1000000条数据):
┌────────────────────────────┐
│        第124234条数据      │ 👀 当前可视区域
│        第124235条数据      │
│        第124236条数据      │
└────────────────────────────┘
其余元素都未渲染,仅在滚动时动态更新。

🧠 为什么不直接全部渲染?

你可以尝试以下代码,渲染 100 万条数据:

<button onclick="renderAll()">一次性渲染100W条</button>
<script>
function renderAll() {
    for(let i = 0; i < 1000000; i++) {
        const div = document.createElement('div');
        div.textContent = \`第\${i + 1}条数据\`;
        document.body.appendChild(div);
    }
}
</script>

⚠️ 问题:

  • 页面的 DOM 元素非常多,导致浏览器卡死
  • 滚动极不流畅,内存暴涨
  • 即便使用 `requestIdleCallback` 分批插入,也治标不治本

✅ 虚拟滚动解决方案

我们只渲染用户能看到的 20~50 条数据,每次滚动时动态更新 DOM 内容,达到「以假乱真」的效果。

✅ 实现步骤

  1. 设置一个固定高度的容器,启用 overflow: auto
  2. 创建一个高度为 总条数 * 单项高度 的“占位容器”
  3. 根据滚动位置,计算出「当前需要显示」的项的起始与结束索引
  4. 更新内部 DOM,仅包含当前这部分数据
  5. 使用 transform: translateY() 精确定位数据偏移量

🧪 示例代码:100 万条数据虚拟滚动渲染

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>虚拟滚动-可运行版本</title>
  <style>
    body {
      margin: 0;
      padding: 20px;
      font-family: sans-serif;
    }
    #container {
      width: 400px;
      height: 600px;
      overflow-y: auto;
      border: 1px solid #ccc;
      position: relative;
    }
    #phantom {
      height: 30000000px; /* total count * itemHeight */
    }
    #render {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
    }
    .item {
      height: 30px;
      box-sizing: border-box;
      border-bottom: 1px solid #eee;
      line-height: 30px;
      padding-left: 10px;
    }
  </style>
</head>
<body>
  <h2>✅ 虚拟滚动:百万条数据</h2>
  <div id="container">
    <div id="phantom"></div>
    <div id="render"></div>
  </div>

  <script>
    const total = 1000000;
    const itemHeight = 30;
    const visibleCount = Math.ceil(600 / itemHeight); // 容器高度为600px
    const buffer = 10; // 缓冲条数
    const container = document.getElementById('container');
    const render = document.getElementById('render');
    const phantom = document.getElementById('phantom');

    phantom.style.height = `${total * itemHeight}px`;

    function renderItems(start) {
      const end = Math.min(start + visibleCount + buffer, total);
      const fragment = document.createDocumentFragment();

      for (let i = start; i < end; i++) {
        const div = document.createElement('div');
        div.className = 'item';
        div.textContent = `${i + 1} 条数据`;
        fragment.appendChild(div);
      }

      render.innerHTML = '';
      render.appendChild(fragment);
      render.style.transform = `translateY(${start * itemHeight}px)`;
    }

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

    renderItems(0); // 初始渲染
  </script>
</body>
</html>

在这里插入图片描述


💡 优化点建议

优化点 描述
使用节流 滚动事件处理使用 `requestAnimationFrame` 或节流函数防抖
异步数据源 可结合分页接口 / IndexedDB 进行局部加载
懒加载图片 虚拟滚动结合图片懒加载可极大降低内存
自定义高度支持 高度不一致时使用虚拟列表 diff patch 技术
React/Vue 框架方案 可用 react-window, react-virtualized, vue-virtual-scroller

🖼️ 伪图示说明

可视区:只渲染40条
─────────────────────
|12423条数据      |
|12424条数据      |
|12425条数据      |
| ......             |
─────────────────────
↑ 滚动后替换 DOM 内容 ↑

📌 总结

  • 虚拟滚动是前端高性能渲染大数据列表的核心技术之一
  • 只渲染可视区域,极大减少 DOM 数量,提升渲染和滚动性能
  • 尤其适合大规模表格、聊天记录、评论流、股票列表等场景

如果你觉得这篇文章有帮助,欢迎点赞、转发或留言交流。下篇文章我们将介绍如何在 Vue/React 项目中集成虚拟滚动组件,敬请期待!

Logo

更多推荐