来,教你开发一款图形编辑器

2,780 阅读7分钟

这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

我是前端西瓜哥,今天教你怎么开发一款图形编辑器。

虽然说的是图形编辑器,其实也归为编辑器,和文本编辑器开发的思想很多地方是共通的。如果你做文本编辑器的开发,也可以看看。

编辑器是比较复杂的项目,由多个功能模块组合而成。要想编辑器的代码有优秀拓展性、可维护性,分模块解耦是非常重要的事情。

编辑器开发的最大工作是在交互上,基本上是围绕鼠标左键事件进行交互,并搭配键盘快捷键。

计算图形学也有涉及,但基本都是简单的知识,比如判断两个矩形是否相交。高级的计算机图形学知识涉及较少,主要是高级功能才会用到,通常我会找第三方库来实现。

所以,不需要太多知识,我们也是可以开发一款图形编辑器的,就是比较花时间。

我们先说说编辑器的几个重要的模块。

坐标模块

说到图形界面,不得不提的就是坐标。

画布是可以缩放拖拽的,真实屏幕上的坐标并不就是编辑器画布上的坐标,所以需要做一层转换。

一个编辑器也可能有多个坐标系统。

画布模块

画布的拖拽移动,以及缩放。

拖拽的实现,可以修改画布左上角坐标,或者用 transform 的 translate。

至于缩放,需要考虑的东西挺多的:

  • 通过鼠标缩放时,要考虑光标所在的位置,以此为缩放中心进行缩放。

  • 考虑最小能缩小到多小,最大能放大到多少。

  • 缩小到画布小于容器大小时,是否将画布居中。

  • 缩放的过程是要设置固定的比例,还是让用户随意缩放出有小数的比例。

  • 一键缩放比例 100% 或自适应窗口的逻辑。

历史记录模块

一款编辑器如果没有撤销重做功能,无论功能多么强大,它就是个半成品。

历史记录的实现通常为设计模式的 命令模式,将各种操作抽象成一个个命令类,在操作过程中创建的命令实例会维护到 undo 栈和 redo 栈中。

在编写交互逻辑时,需要注意 一些有过程性的操作不要生成多个命令

比如移动元素在鼠标释放前,不要生成多个移动命令放入历史记录中,否则撤销不友好。

又比如文本编辑器,快速输入内容时,不要每输入一个字就生成一个操作命令。而是设置一个时间,多长时间没输入才生成命令放到历史记录中。

工具模块

编辑器的独特功能就体现在工具模块上了,其他模块属于是基础建设。

工具模块有什么?最基础的有:

  • 选中移动工具。选中工具其实也很复杂:直接点击选中、选区选中、shift 多选少选、选中已经被选中的元素(移动)
  • 绘制图形工具。矩形、圆形、贝塞尔曲线等。
  • 拖拽画布工具。因为是高频操作。通常一些编辑器额外支持按着右键拖拽,或是按着空格键和鼠标左键拖拽。

工具类很多,那么怎么管理呢?

我们需要一个 toolManager 类,它的实现其实和 Vue RouterReact Router 一样的套路,只是我们不是通过修改路由来切换,而是点击工具按钮调用 API 来切换工具。切换时,对应的注册的工具模块会被挂载。

一个个工具模块,就好比 React 的组件,需要支持组建挂载、销毁等钩子。

在挂载的时候需要注册一些临时的快捷键事件方法,销毁时做事件方法销毁工作。

当然我们这个是交互比较重的编辑器,所以还要实现 mousedown、mouseover 之类的接口。

工具类要注重拓展性,最好做成插件的形式,让其他人可以自定义地添加自己开发的工具类。

图层模块

当一个事物变得复杂后,对其进行管理就非常重要,分组就是一种解决方案。

所以很多编辑器都支持图层。当然如果编辑器足够简单,可以不支持。

图层怎么理解?好比是一张张透明的纸叠在一起,然后我们从上往下看。然后我们分别在不同的纸上画画,顶部的非透明内容会遮挡住下方相同区域的内容。我们还可以隐藏或是移动其中任一图层,调整出我们想要的 “一幅画”。

如果没有图层,我们所有的内容都在一张纸上,我们就失去了很多可能性,基本上修改不能微调,要重画。

图层模块需要支持基础功能:

  • 显示隐藏
  • 锁定(无法操作。当然还有进阶的无法移动、透明像素锁定,可以考虑实现)
  • 选中(就是进入图层)
  • 重命名
  • 删除

还有些高阶也算比较高频的功能:向下合并、创建图层蒙版。

其他模块

  • 快捷键。分为全局快捷键和工具的局部快捷键。我看 hotkey-js 这个快捷键库貌似挺不错的。
  • 标尺模块。画布的顶部和左侧的固定标尺,能够对齐。
  • 元素相对位置辅助线展示
  • ...

技术选型

因为模块非常多,也会为了支持新的功能做一些较大的改动,甚至会做较大的重构。所以弱类型的 JavaScript 还是算了,必须得 TypeScript。

考虑到可移植性,建议将编辑器分为底层能力的 core 和提供交互能力的 UI 层。

这样我们就可以较轻松迁移到 Vue、React 等框架上。就比如 VSCode 的核心编辑器被抽离出为 Monaco Editor,你在 LeetCode 刷题时默认编辑器就是这个 Monaco Editor。

所以你或许可以考虑 monorepo 的多项目开发策略?不过我没有试过。

图形编辑器在底层技术实现上主要有:

  • Dom 元素:svg 或 div。比如 drawio、Boxy SVG、百度脑图。
  • Canvas:比如各种在线协作表格、figma

考虑到通过元素过多导致的网页渲染卡顿,Canvas 可以做渲染上的优化,可以支持大量数据的场景。你可以看各大厂商的在线表格,都是 Canvas 做的,因为只有这样才能支持上万行的表格。

Canvas 是图形编辑器的主流,但在开发上也会变得麻烦:不好调试、且可能需要自己写一套渲染引擎。

需要的知识

因为编辑器很复杂,所以架构要做好,否则无法维护。为此,你需要学习设计模式。

此外你需要学习一些简单的计算机图形学知识,比如图形的碰撞检测、颜色填充的算法等。当你在开发时遇到对应问题再去研究就好了。有些复杂的图形学知识,可能需要找找第三方图形库帮你解决咯。

Canvas 或 SVG 的 API 学习,看你用到什么底层技术了。

一个 demo

哦对了,我有个写了一半的 svg 编辑器项目,欢迎 star。

github.com/F-star/svg-…

处于半废弃状态,大伙看看实现思路就好。

现在主要在写基于 canvas 的编辑器,模仿 figma:

github.com/F-star/suik…

结尾

本文是我开发编辑器的一些粗浅的见解,希望能帮到你。如果你有编辑器相关的问题,可以给我留言,我会考虑将其作为下一篇文章的主题。

如果觉得有帮助,希望你能给我点个赞,这样我才能有动力更新编辑器相关的文章。

本文首发于我的个人公众号:前端西瓜哥