从 DOM 到 Wasm:前端实现千万级表格渲染的技术演进

0 阅读6分钟

在实际开发中,很多人会认为“千万级数据表格”属于不切实际的前端场景,尤其是浏览器端。但在实际业务中,类似飞书文档、Excel Online、乃至企业级 ERP 系统,确实存在这样的需求。

以飞书为例,一个普通在线表格的初始化是 200 行 × 20 列,用户可以随时无限扩展行列,甚至进行协同编辑、插入图文音视频、公式计算、AI 生成等功能。这些都对表格渲染系统提出了极高的性能与架构要求。

本文案例代码仓库


1.0:传统 DOM 表格实现(性能瓶颈的起点)

在早期阶段,我们往往直接使用 <table> 元素,或是使用如 Ant Design Table、Element Plus Table 等组件库,它们底层都依赖原生 DOM 表格结构。代码内容在baseTable.vue

问题很快出现:

  • 当数据量增长至几千行几百列时,性能急剧下滑:

    • 页面卡顿、渲染延迟甚至白屏
    • DOM 节点数爆炸式增长,占用内存飙升
    • 滚动/交互操作响应缓慢

为什么会这样?

因为传统 DOM 表格是“全量渲染”,即便屏幕上只能显示几十行数据,也会将所有数据一股脑渲染到页面,极度浪费资源。


2.0:虚拟滚动表格(Virtual Scrolling)

为了解决上述问题,我们引入了“虚拟渲染”思路:只渲染可视区域内的数据。代码内容在vistualTable.vue

核心思路:

  • <table> 改为 <div> 结构
  • 自行实现类似表格的样式和网格排布
  • 根据滚动位置计算当前“可见行/列”的索引范围,仅渲染这部分数据
  • 引入 overscan(预渲染缓冲区),避免滚动撕裂感

优点:

  • 极大减少 DOM 数量,显著降低内存占用和重排开销
  • 用户体验更流畅,滚动性能提升显著

局限:

  • 当渲染单元格本身逻辑较重(如嵌套组件、图表)时,依然可能卡顿
  • DOM 本身仍有一定上限(比如 1w+ 节点依然可能不稳定)

3.0:Canvas 绘制表格(突破 DOM 瓶颈)

DOM 的结构性和灵活性是优势,但也是性能瓶颈所在。为了进一步提升性能,我们尝试用 <canvas> 实现表格渲染。代码内容在canvasTable.vue

原理:

  • 抛弃传统 DOM,改用 canvas 直接在像素级别绘制单元格、文本
  • 所有单元格渲染逻辑都基于 canvasContext.drawXXX() API

优势:

  • 极致性能:100 万行数据可在 800ms 内渲染完成(相比 DOM 的 12s)
  • 更低内存占用:只需保留数据缓存和一个绘图缓冲区(几十 MB 级别)
  • 不存在 DOM 重排和合成层切换问题

挑战:

  • 内容溢出判断缺失:Canvas 不具备 DOM 那样的自动布局与样式计算能力,无法像 <div> 那样自动检测内容是否超出边界,因此需要开发者手动计算每个单元格文本的宽度与高度(可使用 measureText),并控制截断、省略或自动换行。
  • 缺失原生滚动行为:Canvas 不自带滚动条,不能像 DOM 元素那样通过 overflow 滚动,因此通常需要开发者自定义实现“虚拟滚动条”——包括轨道绘制、拖拽处理、同步 scrollTop/scrollLeft 等。
  • 状态同步问题:由于 Canvas 是“无状态”的像素画布,不具备 DOM 那样的实时属性反映能力,因此选中状态、hover 效果、输入聚焦等交互状态必须在数据模型中单独维护,并在绘制时显式处理。
  • 组件层叠问题:当需要在 Canvas 上叠加组件(如输入框、下拉框)时,必须计算其准确位置,并确保在 scroll 或缩放操作时实时更新位置,避免“组件飘移”或“错位”。
  • 缩放支持复杂:相比 DOM 的 transform: scale,Canvas 缩放涉及多个层面:坐标变换、像素倍率(devicePixelRatio)、字体缩放等,需精细控制渲染比例与锚点,避免出现模糊或绘制错位。
  • 多画布分层策略:为提升性能,实际开发中通常会将表格拆为多个 canvas 层,如:背景层、内容层、选中高亮层、组件层,避免全量重绘,提高渲染粒度控制力。
  • 文本抗锯齿差fillText 渲染清晰度低,可通过 SVG foreignObject 混合模式规避
  • 缺乏事件绑定机制:需手动进行命中检测(点击事件 → 坐标映射 → 单元格索引)
  • 可访问性缺失:需要维护虚拟 DOM 以供屏幕阅读器识别

4.0:Canvas + Tile 分块渲染(性能与体验平衡)

随着数据进一步增长,纯 canvas 的“整屏渲染”仍然面临帧率波动、GPU 压力等问题。我们进一步引入 可视化 Tile 区块渲染机制。代码内容在canvasVirtualTable.vue

技术方案:

  • 将表格逻辑划分为多个“瓷砖(tile)”区域
  • 仅渲染视口区域及其周边缓冲 tile
  • 实现局部更新、惰性加载,提升性能与响应速度

这种 tile-based 渲染模型,已经被飞书、Notion 等广泛采用,是兼顾性能与扩展能力的重要设计。


5.0:WASM 高性能数据处理引擎

尽管渲染优化已较为成熟,数据处理本身(如排序、搜索、筛选、合并)依然是性能瓶颈。尤其在需要生成、处理千万条数据的场景中,纯 JS 已显力不从心。代码内容在webAssemblyTable.vue/canvasWebAssemblyTable.vue,具体构建详情可见下文

WebAssembly(WASM)的优势:

  • 可将核心逻辑用 C++/Rust 编写,编译为 Wasm 模块
  • 运算性能比 JS 快 3~5 倍
  • 可利用共享内存(SharedArrayBuffer)实现零拷贝数据传递
  • 不受 JS 引擎垃圾回收机制影响,内存管理更高效

为什么不是 Web Worker?

虽然 Worker 可将计算任务移出主线程,但:

  • 依旧运行在 JS 引擎之上,速度提升有限
  • 数据传输需要序列化(postMessage),处理亿级数据时极其缓慢

最佳实践:WASM + Worker 混合架构

 Web Worker:负责调度任务,管理 UI 通信
 WASM 模块:在 Worker 中加载运行,处理核心算法逻辑
 SharedArrayBuffer:用于 UI 与计算层之间共享数据,零拷贝通信

技术总览图

技术阶段渲染模式性能瓶颈适用场景
DOM Table全量渲染DOM 数量爆炸小规模数据展示
虚拟滚动可视区域渲染单元格复杂时仍卡顿万级数据轻交互场景
Canvas像素绘制交互开发复杂百万级只读/轻交互表格
Tile Canvas分块绘制动态组件叠加较复杂百万级高频交互表格
Wasm+Worker模块化计算架构集成成本较高亿级数据处理、边渲染边生成场景

总结

千万级数据表格渲染早已不是“不可能完成的任务”。但它要求开发者在架构、性能优化、用户体验、甚至底层图形和内存管理等多方面不断提升。

从传统 DOM 到 Canvas + WASM 混合架构,这背后反映的是前端从页面渲染走向真正的“系统开发”的演进过程。