为什么用 DOM 而非 Canvas 实现画布
问题的起点
需要实现一个图片编辑器。用户可以拖拽照片,调整大小,旋转角度,最后导出为图片。
这是一个清晰的需求。第一个进入脑海的技术方案通常是 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-coding 和 from-intent-to-architecture。
Related Posts
Articles you might also find interesting