百万行 Excel 也不卡:SheetJS 虚拟滚动性能优化新手完整指南
【免费下载链接】sheetjs📗 SheetJS Spreadsheet Data Toolkit -- New home https://git.sheetjs.com/SheetJS/sheetjs项目地址: https://gitcode.com/gh_mirrors/sh/sheetjs
把 8 万行的 Excel 拖进网页后页面直接白屏,是很多前端开发者都做过的噩梦。这篇文章会带你用 SheetJS(老牌开源电子表格数据解析工具)配合虚拟滚动技术,把超大型表格的展示从"秒崩"变成"指哪打哪",全程面向新手,跟着敲就行。
一个真实的周五下午:8 万行表格把页面干崩了 📉
周五下午 4 点,市场部的同事丢来一个 8 万行的销售明细表,要求做成网页在线预览。你信心满满地写了个for循环,把每一行拼成<tr>塞进表格。结果浏览器标签页彻底无响应,风扇狂转三分钟,最后只能含泪点"结束任务"。
这不是你代码写得差,而是思路出了问题——表格数据本身很轻,重的是"把每一行都画出来"这个动作。
浏览器不是 Excel:为什么 DOM 扛不住大表格 ⚠️
先算一笔账:8 万行 × 8 列,等于 64 万个单元格。每个单元格都是一个 DOM 节点,而 DOM 节点是出了名的"内存大户":
| 数据规模 | 单元格(DOM 节点) | 浏览器表现 |
|---|---|---|
| 1 千行 × 10 列 | 1 万 | 尚可 |
| 1 万行 × 10 列 | 10 万 | 明显卡顿 |
| 10 万行 × 10 列 | 100 万 | 白屏 / 崩溃 |
每新增一个节点,都要消耗内存、参与布局计算和重绘。节点一多,主线程就被淹没了。换句话说:不是你代码慢,是"全量渲染"这条路本身就走不通。
换个思路:只画看得见的 30 行 🧠
Excel 桌面版处理上百万行数据毫无压力,靠的不是性能怪兽,而是聪明的取舍:不管表格多长,你屏幕上同时能看到的行数是有限的(通常 20~40 行)。
虚拟滚动(Virtual Scrolling)就是把这个思路搬到网页上:
只创建用户当前能看到的那几十行 DOM,滚动时动态换数据、挪位置;屏幕外的几十万行只是"数字",不占任何渲染资源。
理解这一点,后面所有代码你都看得懂了。
快速上手:用 SheetJS 把表格变成数组 📦
虚拟滚动需要一个"数据源"——一个二维数组。把 Excel 文件变成数组,正是 SheetJS 的看家本领。浏览器里先引入压缩版脚本(项目根目录的dist/xlsx.full.min.js),再在文件读取完成后写这几行:
const wb = XLSX.read(data, { type: "array" }); // 解析整个文件 const ws = wb.Sheets[wb.SheetNames[0]]; // 取第一个工作表 const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }); // 转成二维数组- 第 1 行:把文件二进制解析成工作簿对象
wb; - 第 2 行:从工作簿里取出第一个工作表
ws; - 第 3 行:把工作表转成
[[第一行],[第二行]...]这样的二维数组。
到这里数据源就绪,rows[0]是第一行,rows[10000]是第一万行,取用随心。
动手实操:10 行核心代码搭出虚拟表格 🛠️
接下来是整篇文章最关键的一段代码。思路:滚动时算出"可视区在哪几行",只渲染这几行,并用 CSS 的transform把行"钉"在正确位置:
function render(rows, wrap, rowH, viewH) { const start = Math.max(0, Math.floor(wrap.scrollTop / rowH) - 5); // 起点(上留 5 行缓冲) const end = Math.min(rows.length, start + Math.ceil(viewH / rowH) + 5); // 终点 const frag = document.createDocumentFragment(); // 先拼在内存片段里 for (let i = start; i < end; i++) { const tr = document.createElement("tr"); tr.style.transform = `translateY(${i * rowH}px)`; // 用 transform 定位,跳过看不见的行 tr.innerHTML = rows[i].map(c => `<td>${c ?? ""}</td>`).join(""); frag.appendChild(tr); } wrap.querySelector("tbody").replaceChildren(frag); // 一次性替换 } wrap.addEventListener("scroll", () => render(rows, wrap, 28, 500));逐行翻译一下:
start/end:根据滚动距离scrollTop和行高rowH算出可视行区间,上下各多留 5 行当缓冲,防止快速滚动露白;createDocumentFragment:先把所有行拼进内存片段,最后一次性替换进页面,避免频繁操作 DOM 引发多次重排;translateY:每行按它在整张表里的真实位置做垂直位移,视觉上"各行其位",但页面里始终只有几十个节点;- 最后一行:监听容器的滚动事件,每次滚动重新算一遍。
别忘了两件配套的事:给容器设一个固定高度并开启overflow: auto,再放一个总高度等于"总行数 × 行高"的撑高元素,否则滚动条长度不对。
进阶技巧一:分块读取,别把整张表塞进内存 🧩
如果文件本身就有几十万行,sheet_to_json一次性全量转换也会心疼内存。SheetJS 支持限定范围读取:先用XLSX.utils.decode_range(ws["!ref"])拿到工作表的总范围,再把起始行和结束行改小,配合range参数只转换需要的片段,例如每次只读 1000 行。想看下一页时再往下挪区间,配合后端接口就是标准的"按需加载"。
进阶技巧二:固定行高、缓冲与滚动节流 🎯
- 固定行高是地基:虚拟滚动全靠"行高 × 行号"来定位,行高一变就全乱了。确实需要变高时,要先把每行高度缓存起来,做一张"高度地图"再算位置;
- 缓冲别省:可视区上下多渲染 5~10 行,能明显减少快速拖动时的白屏闪烁;
- 滚动事件记得节流:用
requestAnimationFrame把每帧多次滚动事件合并成一次渲染,主线程轻松一大截。
进阶技巧三:别重复造轮子,直接接数据网格 🚀
如果不想自己写虚拟表格,项目源码里有现成答案。demos/目录收录了大量官方集成示例,其中demos/datagrid/演示了 SheetJS 与 canvas-datagrid(基于 Canvas 绘制的高性能网格)的配合;demos/xspreadsheet/则展示了接入 x-spreadsheet 这个类 Excel 编辑器。用 React 或 Vue 的读者,还可以看demos/react/和demos/vue/里的接入方式,原理都是"数据喂给组件,组件自己处理虚拟化"。
避坑清单:新手最常问的 5 个问题 🔍
Q1:多少行算"大"?什么时候必须上虚拟滚动?经验值:超过 1000 行就该考虑,超过 1 万行建议直接上。DOM 节点数超过一两万个时,肉眼就能感到卡顿。
Q2:明明写了虚拟滚动,为什么滚动到底还是全量渲染?十有八九是忘了放"撑高"元素,滚动条长度不对导致滚动事件异常;或者替换节点时用了appendChild累积了旧节点。检查容器内是否始终保持"可视行数"规模的节点量。
Q3:raw: false是什么意思?它是sheet_to_json的选项:false表示按单元格显示格式取值(日期变成"2026/8/20"这种),true表示取原始值(日期是一串时间戳数字)。做预览用false,做数据计算用true。
Q4:数字变成科学计数法或精度丢了怎么办?Excel 存超长数字(比如 20 位单号)时容易出这种问题。预览场景用raw: false按文本格式读,能保住显示效果;需要精确计算的场景,考虑按字符串读再自行处理。
Q5:要不要上 Web Worker?解析超大文件(比如几十 MB 的 xlsx)时值得放 Worker,避免解析过程卡住页面;渲染本身留在主线程即可,虚拟滚动已经把渲染量压得很低了。
资源与延伸阅读:在项目里能直接看什么 📚
README.md:项目总览,里面还标注了官方文档与交流入口;package.json:当前版本为 0.18.12,主入口是xlsx.js;dist/xlsx.full.min.js:浏览器可直接引用的压缩版;demos/:官方示例总目录,覆盖 React、Vue、Angular、服务端等场景;demos/datagrid/与demos/xspreadsheet/:数据网格集成示例;demos/server/:Node 服务端处理表格的示例;test_files/:测试样本文件目录,也可以把你要调试的大表格放进去当实验田;types/index.d.ts:TypeScript 类型定义,用 TS 的同学照着手写类型。
下一步:现在就动手试试 🎬
光看不动是学不会的。建议这样安排今晚的 30 分钟:
- 用
git clone https://gitcode.com/gh_mirrors/sh/sheetjs把源码拉下来,或直接在项目里新建一个 HTML 页面引用dist/xlsx.full.min.js; - 打开浏览器的开发者工具 → Performance 面板,先渲染 1 万行,再渲染 10 万行,对比两种做法的帧率与内存;
- 把你日常工作里最大的那份表格丢进来,用本文的代码跑一遍,感受"秒开"的快乐。
等你亲眼看到 10 万行数据在页面里流畅滚动时,你会回来感谢那个周五下午的自己。
SheetJS 虚拟滚动实战:只渲染可见行,10 万行 Excel 也能秒开不卡顿。
【免费下载链接】sheetjs📗 SheetJS Spreadsheet Data Toolkit -- New home https://git.sheetjs.com/SheetJS/sheetjs项目地址: https://gitcode.com/gh_mirrors/sh/sheetjs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考