为什么用 DOM 而非 Canvas 实现画布

2 min read
Zekari
技术选型前端架构DOMCanvas决策方法

问题的起点

需要实现一个图片编辑器。用户可以拖拽照片,调整大小,旋转角度,最后导出为图片。

这是一个清晰的需求。第一个进入脑海的技术方案通常是 Canvas。Photoshop 用 Canvas,Figma 用 Canvas,几乎所有专业的图像编辑工具都基于像素级的渲染。这似乎是唯一正确的选择。

但 Purikura Studio 最终选择了 DOM。

这不是因为 DOM 更好。而是因为在特定的约束条件下,它是更合适的选择。

自然的选择:Canvas

Canvas 的优势是显而易见的。

性能:直接操作像素,渲染速度快。当画布上有数百个对象时,Canvas 依然流畅。DOM 在这种情况下会开始卡顿。

控制力:可以精确控制每个像素的颜色。复杂的视觉效果,比如模糊、混合模式、粒子系统,都可以直接实现。

成熟的生态:Fabric.js、Konva、Paper.js 这些库已经解决了大部分常见问题。不需要从零开始。

这些优势是真实的。对于专业级的图像编辑工具,Canvas 几乎是必然选择。

但 Canvas 也有成本。

事件处理:Canvas 只是一个画布,浏览器不知道你在上面画了什么。点击、拖拽这些交互需要自己计算鼠标坐标,判断是否在某个对象的边界内。虽然库可以帮你做这些,但引入库本身也是成本。

布局系统:Canvas 没有布局的概念。如果需要响应式设计,需要自己计算每个元素在不同屏幕尺寸下的位置,然后重新渲染。

可访问性:屏幕阅读器看不到 Canvas 里的内容。要支持无障碍访问,需要额外维护一套 DOM 结构。

这些不是 Canvas 的问题。Canvas 从设计上就是为像素级操作服务的,不是为 UI 组件服务的。

另一种可能:DOM

DOM 通常不被认为是实现画布的选择。它太慢,不够底层,没有像素级的控制力。

但 DOM 有一些 Canvas 没有的能力。

布局系统:Flexbox 和 Grid 提供了成熟的布局能力。画布需要自适应屏幕尺寸?浏览器帮你计算。不需要自己写布局算法。

事件系统:每个 DOM 元素都是独立的事件目标。点击、拖拽可以直接绑定。浏览器会告诉你用户点击了哪个元素,不需要自己判断坐标。

CSS 的力量:变换、过渡、动画都有现成的 API。transform: rotate(45deg) 就能旋转元素,不需要重新计算每个点的坐标。

可访问性:默认支持。屏幕阅读器能理解 DOM 结构。

这些能力在简单场景下是优势。如果画布上的对象数量有限,交互不复杂,DOM 可能是更简单的选择。

如何权衡技术选型

技术选型的核心是权衡。没有完美的方案,只有在特定约束下更合适的方案。

理解约束条件

Purikura Studio 的约束条件:

团队规模:小团队,需要快速迭代。学习 Canvas 库、调试渲染问题、处理浏览器兼容性,这些都是时间成本。

使用场景:照片拼贴,不是专业图像编辑。画布上通常有 4-16 个照片,不会有成百上千个对象。不需要复杂的视觉效果。

交互复杂度:拖拽、缩放、旋转,这些都是基础交互。不需要像素级的精确控制。

响应式需求:需要在不同屏幕尺寸上工作。手机、平板、桌面都要支持。

这些约束条件指向了 DOM。

评估风险

选择 DOM 的风险:

性能上限:当对象数量增加,DOM 的性能会下降。如果未来需要支持更复杂的场景,可能需要重构。

导出问题:DOM 不是图像,导出时需要转换。这增加了一个转换步骤,可能引入新的问题。

动画性能:如果需要复杂的动画效果,DOM 的性能可能不够。

这些风险是真实的。但它们是可以接受的。因为当前的使用场景还没有触及这些上限。

做出决策

技术选型不是选择最好的方案,而是选择风险可控的方案。

DOM 的风险:性能可能不够,未来可能需要重构。 Canvas 的成本:学习曲线,开发时间,维护复杂度。

在小团队、快速迭代的约束下,DOM 的风险更可控。因为可以先用 DOM 验证产品方向,如果真的遇到性能瓶颈,再考虑重构。而如果一开始选择 Canvas,开发周期会更长,可能还没有验证产品方向就已经耗尽资源。

这就是决策的逻辑:不是选择最好的,而是选择在当前约束下最合理的。

实践中的验证

选择 DOM 后,遇到了一些预料之中的问题。

导出质量:使用 html-to-image 库将 DOM 转换为图片。但某些 CSS 属性不支持,比如一些混合模式。解决方法是在导出前简化样式。

性能边界:当画布上有超过 30 个元素时,拖拽开始变得不那么流畅。解决方法是在拖拽时禁用一些不必要的重绘。

移动端兼容:触摸事件和鼠标事件的处理逻辑不完全一致。需要额外处理触摸手势。

这些问题都在可接受范围内。因为它们都有解决方案,只是需要额外的工作量。

同时,DOM 也带来了预料之中的优势。

快速迭代:添加新的布局模板只需要调整 CSS Grid 的配置,不需要重新计算每个元素的位置。

调试便利:可以直接在浏览器开发者工具中查看 DOM 结构,调试 CSS。不需要在 Canvas 里打日志查看坐标。

响应式支持:几乎不需要额外工作,浏览器自动处理不同屏幕尺寸的布局。

在实践中发现的 DOM 性能边界:

元素数量:超过 30 个可交互元素时,拖拽开始卡顿 变换频率:高频率更新 transform 属性(如拖拽)会触发重排,影响性能 复杂样式:使用 filter、backdrop-filter 这些属性会显著降低性能 导出尺寸:导出大尺寸图片(超过 4000x4000)时,转换过程会很慢

如果你的使用场景会触及这些边界,Canvas 可能是更好的选择。

当前的 Purikura Studio 还没有遇到这些瓶颈,但这不代表未来不会遇到。这是选择 DOM 时需要接受的技术债。

更多关于整体架构的思考,可以参考 studio-frontend-architecture

决策背后的价值观

技术选型本质上是价值观的体现。

选择 Canvas 的价值观:性能优先,专业为重。愿意投入更多时间获得更好的控制力和更高的性能上限。

选择 DOM 的价值观:迭代优先,简单为重。愿意接受性能上限的限制,换取更快的开发速度和更简单的维护成本。

没有哪种价值观是绝对正确的。它们只是在不同的约束条件下有不同的适用性。

对于 Purikura Studio,在小团队、快速验证产品方向的阶段,选择 DOM 是合理的。如果未来产品验证成功,需要支持更复杂的场景,再重构为 Canvas 也不迟。

这也是技术选型的一个重要原则:不要为未来可能出现的需求过度设计。专注于当前的约束条件,做出在当前阶段最合理的选择。未来的问题留给未来解决。

最后

技术选型没有标准答案。Canvas 和 DOM 都不是银弹。

重要的是理解约束条件,评估风险,然后做出在当前阶段最合理的选择。

选择 DOM 而非 Canvas,不是因为 DOM 更好,而是因为在小团队、快速迭代、简单场景的约束下,它的风险更可控。

这就是技术决策的本质:在不完美中寻找最合理的平衡点。

这个决策过程中仍有一些未完全解决的问题:

迁移路径:如果未来确实需要迁移到 Canvas,迁移成本会有多大?当前的架构没有预留这个迁移路径,这是一个已知的技术债。

性能优化空间:DOM 的性能优化已经做到什么程度?是否还有进一步优化的空间?还是已经接近 DOM 的性能上限?

混合方案:是否可能将 DOM 和 Canvas 结合使用?比如用 DOM 做布局和交互,用 Canvas 做最终渲染?这样是否能兼得两者的优势?

这些问题目前没有答案。但它们的存在本身就说明,技术选型不是一次性的决策,而是一个持续的过程。

更多关于技术决策方法的思考,可以参考 thinking-before-codingfrom-intent-to-architecture