Univer:基于Canvas与Web Worker的开源电子表格引擎解析

发布时间:2026/11/3 4:02:32
Univer:基于Canvas与Web Worker的开源电子表格引擎解析 1. Univer是什么为什么一个开源表格项目能火遍全球这段时间GitHub上有个项目增速非常夸张star飙升速度一度超过了很多老牌开源软件名字就叫 Univer。如果你做过在线表格、数据处理、低代码平台这类前端方向的东西那你大概率已经在某个技术群里看到过有人讨论它了而且讨论的点往往集中在“反应快”“内存开销小”“居然能解析Excel大文件”这类很具体的体验上。Univer简单来说是一套基于Web的开源电子表格引擎。它最核心的能力就是在浏览器里渲染出一个类似Excel的完整表格界面支持编辑、公式、图表、样式、多Sheet切换、打印、协同编辑还能直接把.xlsx文件拖进来打开也可以在页面里操作完后导出成.xlsx给用户下载。听起来像是“又一个Web Excel”但真正让我觉得它值得聊的是它的技术选型不依赖DOM来做表格单元格渲染而是直接用Canvas把整个表格“画”出来。这带来的直接好处就是你在里面滑几万行、操作几千列的表格交互还能保持比较流畅的水平。它适合谁看我建议下面这几类朋友重点留意一下前端开发、全栈开发想给自家产品嵌入一个多Sheet表格组件而不是花钱买商业套件或者自己从零手写一个表格。技术选型决策者/架构师正在评估“在线表格能力要不要自研”或者对比商业方案、开源方案、拼装方案的成本。数据应用从业者做ERP、财务系统、低代码平台、报表BI需要给用户一个“像Excel一样”操作数据的界面。对大型前端应用架构感兴趣的人Univer的Canvas渲染、Web Worker计算、命令系统设计都很有学习价值拆开看一遍挺长见识。现在还有一个关键词叫“univer在线”这其实是社区对Univer应用形态的进一步期待。Univer不光是一个可以在项目中嵌入的组件它还提供了一套完整的前端侧基础能力意味着你可以基于它搭建出一个完整的在线协作办公应用而不是只能把它当控件塞进表单里。开源做基础能力、再在你自己的业务里拼出在线办公产品这条路径放在国内团队里能做到这个完整度的项目是不多的。下面我会从技术核心、架构设计、完整实操、踩坑记录和选型建议这几个角度把这个项目讲透。2. 架构与设计Canvas渲染、Worker计算和命令系统如何协同工作很多人第一次看到Univer的源码时会觉得它挺复杂因为它不是一个简单的“表格组件”而是一整套分层清晰的架构。我给你拆开来说。2.1 渲染层Canvas分层策略是怎么扛住大数据量渲染的传统表格组件比如早年很多jQuery插件、Angular表格控件的做法是用DOM来渲染每一个单元格每个格子一个td或者div。这在几百行数据时问题不大一旦到几千行你就会发现性能直线往下掉因为DOM节点数量太多了每一次重排、重绘都要浏览器付出很大的代价。Univer的做法完全不同。它在底层把整个表格组件当成一个“场景”来绘制单元格、边框、选区、行号列号、滚动条等等内容全部用Canvas分图层绘制。你可以把它理解为游戏引擎的思路——游戏里面那么多角色和场景不是靠HTML标签堆出来的而是每一帧都重新绘制到画布上。Univer把滚动、选中、浮动元素等再拆成不同图层Window渲染器、Sheet组件渲染器、普通单元格渲染器、富文本渲染器各管一摊绘制时只对脏区域做局部重绘所以就算数据量大视觉上也很难感觉到生硬闪烁。这里有个容易被忽略的细节Canvas绘制的文本、边框在浏览器缩放时会模糊或者发虚Univer在实现里做了大量高DPI适配层面的处理包括监听devicePixelRatio变化、手动设置canvas的实际尺寸再通过样式尺寸缩放。我第一次集成到项目里发现高清屏下显示很清晰还以为是内置了高清字体方案后来看源码才发现是这一层做了很细腻的适配。2.2 计算层公式引擎为什么放到Web Worker里表格应用最消耗CPU的操作除了渲染就是公式计算和单元格依赖链更新。比如你在一张几千行的表里写了一个SUM(A1:A5000)如果计算在主线程上进行用户拖动滚动条或者输入内容时页面会明显出现卡顿因为主线程被公式计算占用了。Univer把公式引擎、数值计算逻辑独立出来放到了Web Worker线程中去执行主线程只负责收集操作事件、更新渲染状态计算结果再异步地回到渲染层显示。这就是为什么在Univer里输入一个复杂的公式尤其是有跨Sheet引用的公式页面整体还能保持相对跟手。还需要注意一点公式引擎并不是简单地对每个格子写死一个计算函数它需要维护一套完整的依赖关系图。假设A1的值被修改那么所有引用了A1的公式都需要重新求值而同时这些公式还可能被其他公式引用所以需要做依赖链的拓扑排序和动态更新。Univer在架构上把公式的注册、依赖分析、计算调度都做成了模块化的服务开发者还可以注册自己的自定义函数并通过API调用计算结果。2.3 状态管理层Command命令模式如何支撑撤销重做和协同不少前端开发者第一次集成Univer时会有一个灵魂困惑代码里几乎看不到“直接改某个单元格数据”的写法。Univer把操作封装成了可序列化的Command例如SetRangeValuesCommand负责设置区域单元格值AddWorksheetCommand负责新增Sheet。每一次操作都先发给CommandService由命令处理器来执行并且每个命令都会自动进入撤销栈、重做栈。这个设计对在线办公场景太重要了。因为只要是命令式的任何事情都天然可撤销、可重做这个问题在业务里要是自己手动去维护历史版本复杂度会非常惊人。更厉害的是命令本身可以序列化为一段JSON这意味着客户端可以把命令发给服务端服务端再做合并和广播——这是协同编辑的基础条件。你在界面上做一个操作它对应用户来说就是一个可同步的消息而不是把整张表格的数据全量发送过去。另外如果你需要在外部给Univer发指令比如在业务系统里面某个按钮触发“给选中单元格填充一个数字”也是先把一个命令对象交给CommandService去执行。这种“操作全部走命令”的模式还天然方便做权限控制、操作审计只要在命令通道里加一层拦截器所有用户的动作都能被记录。2.4 协同编辑多人同时操作时冲突怎么解决协同是Univer一直强调的能力它的协同方案基于OTOperational Transformation同时又支持用服务端协调来自不同用户的命令。很多人对OT的理解容易停留在“CRDT也很火、是不是比OT好”这个问题上我的看法是表格场景下OT仍然够用且效率更高。因为表格的数据结构是扁平的矩阵不像文档那样有复杂的插入删除节点操作每个命令的作用区域清晰做变换和冲突合并的算法成本相对低。Univer的服务端协同框架同样是开源的里面集成了文档版本管理、命令合成、冲突处理。一个个用户的编辑命令进去之后服务端会为每个命令生成一个版本号并管理所有客户端的并发编辑。这里我多说一句如果你只把Univer当前端组件用协同模块可以不启用直接跑单机模式但如果你是做在线文档类产品协同模块会让整个方案完整度高出很多。3. 从零接入一个Vite项目中集成Univer的完整实操理论说再多不如动手跑一遍。我拿一个Vite TypeScript的空项目来演示怎么把Univer从零接入并让它展示一个带公式的工作簿。3.1 初始化工程与安装依赖先创建一个Vite项目npm create vitelatest my-univer-demo -- --template vue-ts cd my-univer-demo npm install接着安装Univer的核心依赖。注意Univer把各种能力拆成了多个子包这样做的好处是你可以按需引入避免一次性把整套东西都打进来。基础场景至少需要下面几个包npm install univerjs/core univerjs/design univerjs/ui univerjs/sheets univerjs/sheets-formula univerjs/sheets-numfmt当前版本的API设计是core包提供创建Univer实例的基础能力ui包负责渲染整个界面框架sheets是表格功能主体sheets-formula提供公式支持sheets-numfmt负责数字格式。如果你要做图表、打印、协同还要另外装对应的包——这也是Univer一个比较显著的特点它更像一套积木按需拼装。3.2 创建Univer实例并注册核心插件在你的入口文件比如main.ts里初始化一个Univer实例import { Univer } from univerjs/core; import { defaultTheme } from univerjs/design; import { UniverUIPlugin } from univerjs/ui; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverSheetsFormulaPlugin } from univerjs/sheets-formula; import { UniverSheetsNumfmtPlugin } from univerjs/sheets-numfmt; import univerjs/design/lib/index.css; const univer new Univer({ theme: defaultTheme }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsFormulaPlugin); univer.registerPlugin(UniverSheetsNumfmtPlugin); univer.registerPlugin(UniverUIPlugin);这里有两个容易踩的坑。第一个是CSS不引入会导致界面整体错乱Univer的UI是依赖它自己的设计系统样式的漏了CSS你会看到一个分组方式很诡异的布局。第二个是注册顺序我建议顺序保持core基础 - sheets - 公式/数字格式 - UI虽然部分权威文档说UI插件可以放前面但从我实际运行的经验来看这样注册的稳定性最好。3.3 创建工作簿、写入数据、使用公式创建实例之后需要手动创建一个Workbook。下面的代码会创建一个包含3个Sheet的工作簿并且在第一个Sheet里填充字符串、数字和公式import { LocaleType } from univerjs/core; const workbook univer.createUniverSheet({ id: workbook-demo, locale: LocaleType.ZH_CN, sheets: [ { id: sheet-1, name: 销售明细, rowCount: 100, columnCount: 20, cellData: { 0: { 0: { v: 商品 }, 1: { v: 数量 }, 2: { v: 单价 } }, 1: { 0: { v: 键盘 }, 1: { v: 120 }, 2: { v: 89.9 } }, 2: { 0: { v: 鼠标 }, 1: { v: 200 }, 2: { v: 39.9 } }, 3: { 0: { v: 合计金额 }, 2: { f: B2*C2B3*C3 } } } } ] });这里cellData的行号、列号都是数字索引从0开始。v表示单元格显示值f表示公式字符串。你可能会注意到第三行我用的是0: { v: ... }, 1: { v: ... }这种嵌套结构这是Univer里比较标准的单元格数据格式第一层是行第二层是列第三层是单元格对象。如果你直接用A1这种Excel坐标来索引反而没有这么直观。公式有个重要细节在初始化时写入的公式不会立刻在界面上看到计算结果的值Univer会经过一个异步计算周期。如果你要用公式结果做后续业务逻辑例如读取合计金额的值给后端一定要注册监听或者等待公式计算完成直接在初始化后同步去读取单元格值可能拿到的是空字符串。3.4 数据导入导出与常用交互配置实际业务里用户大概率不是从空表格开始而是要打开已有的Excel文件。Univer提供了一套文件导入导出能力当前版本推荐使用包univerjs/sheets-xlsxnpm install univerjs/sheets-xlsx注册到Univer实例上然后给Univer实例传入一个配置让它能监听文件拖拽import { UniverSheetsXLSXPlugin } from univerjs/sheets-xlsx; univer.registerPlugin(UniverSheetsXLSXPlugin);拖拽导入这块内置了是比较完整的把.xlsx文件拖进表格区域Univer会解析文件并自动重建工作簿结构。如果你要做自定义的导入按钮也可以调用插件提供的底层解析方法拿到Sheet数据后塞给createUniverSheet。导出操作同样由插件提供调用时它会自动把当前工作簿的所有Sheet、样式、公式、合并单元格信息一并写入新的.xlsx文件并触发下载。我自己在项目里实测过包含合并单元格、条件格式、图标集的中型Excel文件300KB左右打开和重新导出的还原度非常高但如果是包含复杂图表、VBA宏这些高级特性的文件会有一定的能力丢失这个必须提前和需求方对齐不能等用户导出了才发现。4. 实战踩坑记录我在集成Univer时遇到的六个典型问题任何开源项目跑通demo只是第一步真正做业务集成时问题才会一个个冒出来。我把我在项目里遇到的主要问题整理一下这些问题网上讨论得比较零散我集中写出来做个速查表。问题表现原因解决方案中文字体白屏单元格里的中文不显示或者显示成方框Canvas绘制文本依赖font-family字体未加载完成时绘制失败提前加载字体并确保font配置正确页面里先渲染一个隐藏中文字体预览包体积偏大首次加载JS bundle超过1MB弱网下体验差默认全量引包UI、公式、渲染组件都打进来了按需注册插件对静态资源做CDN拆分配合Webpack/Vite做manualChunks大数据量能动但卡顿10万行数据滚动不流畅单元格输入掉帧Canvas再强也扛不住频繁的整表重绘命令触发的渲染范围过大使用Univer的区间更新接口尽量对局部区域做setRangeValues减少频繁的全量选区刷新与React/Vue集成边界模糊用框架方式写表格单元格内容时总是失效Univer的单元格绘制不是DOM框架的vdom/directive能力不适用理解Univer渲染模型业务UI用框架做单元格交互用Univer命令与事件完成协同服务搭建困难开箱只有前端联调时找不到服务端协同需要自建服务端并部署Univer提供的基础协同服务优先用Univer官方的协同服务包做不了OT就先用“锁全量刷新”过渡版本API变化快照着某篇文章写的代码升级后报错Univer核心API在快速演进期社区资料滞后锁版本使用文档与源码对照关注CHANGELOG4.1 中文字体白屏与字体加载问题这个问题在本地开发时很少暴露因为开发机器上中文字体都是系统自带的。但你的应用跑在用户浏览器里时情况完全不同浏览器不会自动加载系统字体Canvas在绘制文本时需要指定一个已经可用的字体。如果你不做任何处理Univer初始化后页面里的中文大概率是空白。我的处理方式是在应用入口处先用document.fonts.load(16px PingFang SC)这类方式主动加载中文字体加载完成后再调用univer.createUniverSheet(...)。如果你对部署体积不敏感也可以用font-face嵌一个字体子集进去保证跨平台、跨设备都能稳定显示。这里不要觉得多此一举——在线表格产品里没有中文字体等于整个产品不可用。4.2 包体积过大按需引入与CDN加速Univer的核心包体系拆得很细但如果你图省事把全部插件都注册了打包产物会立刻膨胀。默认的引入方式光univerjs/ui加univerjs/sheets两个包产物就能到六七百KB这还不算图表的扩展包。建议的做法是业务里只注册真正用到的插件。比如不需要协同就把协同相关包全部去掉。使用构建工具的拆包能力把Univer相关代码打进一个独立chunk让它在用户二次访问时走强缓存。如果面向企业内网用户把Univer的静态资源放到自己的CDN或者OSS上首次加载的体验会好很多。官方支持通过CDN方式引入适合快速原型验证但生产环境我建议还是用npm包配合构建工具做tree-shaking更可控。4.3 大数据量渲染卡顿别把Canvas想得太神Canvas渲染的确比DOM方案承载力大不少但它不是万能的。我曾经在测试环境构造了一张15万行、30列的表格前5万行感觉还行但从10万行开始继续往下滚动帧率就明显下来了。分析下来主要原因不是绘制本身而是大量单元格在滚动过程中需要动态计算可视区域和样式合并这个计算量级上去了主线程就开始吃紧。要改善性能关键是减少高频操作触发的全量更新范围。比如不要频繁去调用读取整表数据的API更不要在外层循环里对成千上万个单元格挨个setRangeValues。改成批量把数据组织成矩阵后一次性写入性能会有非常可观的提升。4.4 与React生态集成的边界问题有一些团队想用Univer做一个表单里的“可编辑Excel表格”于是希望在React组件里直接控制Univer单元格的状态。但实际上Univer内部的数据模型和React组件的model并不是同一个东西你在React里setState不会自动触发Univer重新计算公式、重绘画布。正确的做法是把Univer当成一个独立的“黑盒”React负责外层的布局Univer自己管理自己的数据。二者如果需要通信通过Univer的命令API和回调事件来做而不是强行把Univer的状态挂到React的响应式系统里。这样做还有一个附带的好处如果你以后再换业务框架比如转到Vue或者原生JSUniver这一层并不受影响。4.5 协同服务的二次开发复杂度Univer的核心竞争力之一是协同但这里要提醒一句“协同”不是一个开箱即用的功能。前端开箱即用的只是协同客户端的协议支持服务端需要你自己部署和配置。如果你的业务是登录用户在小范围共享一张表格部署一套同步服务不是大问题但如果产品涉及大规模多租户协同你要考虑房间管理、权限校验、命令冲突频率、服务端存储模型设计——这就不是前端团队的纯技术活了。我建议刚接触Univer的团队第一个版本先不考虑协同把表格的编辑、导入导出、样式还原做好。协同这种能力等到产品核心流程跑通后再迭代进来。如果一开始就把协同架构搭进去排期和复杂度都会失控。4.6 版本API变动频繁升级踩坑注意事项Univer目前还处于快速迭代期版本之间的小版本升级都可能带来不兼容变更API命名和模块组织都在不断调整。社区里很多文章写得很好但你照着文章敲代码会发现有些包已经找不到或者某个类被移到了别的目录。我给你的建议是进入项目期就锁版本号不要用^裸依赖升级时先看官方的CHANGELOG再去源码里搜对应API的最新用法。如果你在社区看中了某个独立功能比如某篇文章教你如何自定义一个渲染器记得先确认该文章的版本日期和你的依赖版本是否对得上。5. 这到底适不适合你的项目选型建议与二次开发路线最后聊一聊选型和投入。我在前文已经反复提到Univer是一个引擎级项目不是插上就能用的成品。那么什么样的项目适合直接引入什么样的项目要慎重这里给一个比较明确的画像。5.1 适用场景画像数据填报、审批系统需要用户在一个仿Excel界面里录入多行结构化数据同时对数据格式做约束。Univer开箱即用的编辑体验足够还能通过API获取填完的单元格数据提交给后端。嵌入式数据分析表格在BI看板里增加“在线编辑”能力用户看报表的同时能自己拉公式、算小计、调整格式。Univer的上手成本比接商业套件低功能覆盖比手写一个jqGrid高好几个维度。在线教培场景教育产品里需要让学生和老师共同操作一张表格做实验记录、项目分工表之类的东西。Univer的协同能力虽然要自建服务但整体方案是完整可行的。低代码/零代码平台需要给搭建者提供一个表格类组件并且组件能暴露数据绑定、事件回调。Univer在架构上就是为这种可嵌入场景设计的命令系统还方便做操作日志。5.2 不适用场景对Office文档格式保真要求极高的场景如果你的核心用户群是重度Office用户要求Excel文件里的每一处细节包括图表、宏、打印样式、数据透视表等都必须1:1还原那Univer目前还达不到商业Office套件的兼容级别。这时候还是要选商业化的在线Office方案。只为了展示数据不需要编辑如果你的需求只是把数据呈现成表格那用普通的HTML表格配合antd/ElementPlus这类组件库就够了完全没必要引入一个几十万行代码的引擎。团队没有前端基建能力Univer二次开发需要团队能看源码、能调样式、能理解Canvas渲染机制。如果团队只停留在“调用组件API”的层面遇到性能或兼容性问题会很难处置。对移动端体验优先级极高Univer的核心优化集中在桌面浏览器场景。虽然它在触屏设备上也能操作但和原生移动端表格应用比手势细节、输入体验都有差距。5.3 团队二次开发需要备哪些“粮草”如果你决定用Univer做产品我建议在团队内部先建立三个方面的工作流。第一是知识沉淀安排至少一个核心成员完整阅读Univer的架构文档把“绘制流程”“数据模型”“命令系统”“插件注册机制”这四个部分梳理成团队内部的PPT或者文档。未来所有集成问题大概率都能在大方向上归结到这四个部分。第二是调试工具链由于Univer的界面是Canvas渲染传统的前端DOM调试几乎不适用。你需要熟悉它的内部日志能力在初始化时开启调试模式一旦界面出现异常能很快定位是渲染层的问题、数据层的问题还是命令执行中出现异常。一个顺手的方式是拿到报错信息后先查对应的Command名称再顺着命令源码比对自己的调用方式。第三是渐进式引入策略不要一口气把Univer塞进核心业务流程里。先做一个独立页面只做“上传Excel预览”这个功能验证包体、加载时间、兼容性然后做“编辑并导出”最后再做在线保存、多Sheet联动。每一步都单独上线让团队逐步熟悉它的脾性。我个人的体会是Univer这套技术栈几乎就是当年WebOffice产品从零到一所需要的“最大公约数”。它把渲染、公式、协同协议这些最难啃的骨头都做成了开源可用的形态剩下的业务集成越扎实你越能感受到这个项目的潜力。这个方向值得长期投入因为它不只是一个表格控件而是一整套Web办公能力的底座。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询