老牌开源 SVG 编辑器 SVGEdit 是如何架构的?

大家好,我是前端西瓜哥。这次简单看看 SVGEdit 的架构。

SVGEdit 的版本为 7.2.0。

SVGEdit 一款非常老牌的 SVG 图形编辑器,用于编辑处理 SVG,start 数目前是 5.8k。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第1张图片

它的优点在于经过多年的开发,完成度高,较为成熟,功能相当丰富

  1. 有丰富的工具:选择工具、铅笔工具、钢笔工具(三阶贝塞尔)、直线、各种图形、图片、文字等;
  2. 画布缩放、图形缩放旋转、编组、复制粘贴、层级排布修改、对齐;
  3. 网格线、标尺、图层管理、导入导出 SVG;
  4. 历史记录,支持撤销重做(编辑器的基本功能)等等。

缺点是几乎不维护了,提交很少,大概一个月一提交,最新版 7.2.0 也是 22 年 8 月的时候了。

UI 比较简陋,很简单就能看到一些 UI bug。

如果要找一个 UI 好看的,可以看看开源项目 :Method-Draw,这个 UI 好看很多。它 fork 了 SVG-Edit 并做了一些改造。Method-Draw 标榜简单易用,所以有意去掉了 SVGEdit 的一些高级功能,比如图层。

技术栈

Web Component + SVG + Rollup + i18next

UI 使用了 Web Component,浏览器原生支持的组件方案。

作为一款 SVG 编辑器,选择 SVG 没有毛病,这样渲染效果就完全交给浏览器,不需要根据标准去实现渲染效果,自己专心写编辑器的业务逻辑即可。

没有用 TypeScript,因为是很老的项目,当时 TypeScript 尚未大行其道。如果要做新项目,建议还是上 TypeScript,大型复杂软件还是很需要类型系统的。

打包用了 Rollup。国际化用了 i18next。

模块设计

UI 层对应 Editor 类,该类继承了 EditorStartup 类,后者做一些初始化操作。

编辑器内核对应 SvgCanvas 类。

SvgCanvas 感觉太多读写属性的方法(getXx 和 setXx)了,学 Java 学的。可以抽几个小类把耦合性强的方法封装起来。

有插件机制,插件对象通过 addExtension 方法注入到 SvgCanvas.extensions 对象。

可以看到注册了 grid(网格)、markers(标尺)之类的插件。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第2张图片

关于 UI 层和内核层的通信,UI 层改数据,会直接改内核层,然后再改 UI 层。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第3张图片

这里的 zoom 有两个数据源,可能会出现改了一个忘记改另一个的情况。建议只使用一个内核层数据源,改这个数据源后通过事件通知 UI 层或其他层做数据同步。多数据源是坏文明。

渲染方案

渲染方案是 SVG。

SVG 编辑器用 SVG,相当合理。

对于图形树的实现、图形拾取(点选)、图形渲染,SVGEdit 都交给浏览器都去实现。

SVG 对一般前端开发是非常好上手的,不需要太多图形知识,本质就是一个有层级的 DOM 元素树,前端同学再熟悉不过了,只是元素专门用来描述图形。

但因为远离底层,不方便做一些渲染优化和缓存,图形多的时候很卡,不适合做高性能图形编辑器。

灵活性也较差,比如一个 0.5 线宽的直线还把画布缩小了,根本就点不中好不好,希望点击区域可以外扩一些,或想点中一个设置为不可见的图形点击区域。这个做不了。

当然一个可以考虑的方案是 SVG 只是单纯做渲染,图形拾取自己实现。

但 SVG 有一个强大的优点:方便做功能扩展,进行二次开发

比如你要在图形编辑器里加一个新的模块,比如倒计时、一个表单组件,网上找到轮子集成进去会很方便。因为 SVG 里面可以嵌入 DOM 元素,DOM 元素里也可以嵌入 SVG。

UI 层

UI 层原本是基于 jQuery UI 的,但后面丢弃 jQuery 改用 Web Component 进行了重构。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第4张图片

顺带一提,有个叫做 jPicker 的基于 jQuery 的拾色器插件,也做了魔改,去掉对 jQuery 的依赖。

它没有用 React、Vue 这些 UI 框架,而是选择 Web Component,我认为这是一个糟糕的决策。

Web Component 虽然被 浏览器原生支持,但并不是主流,生态一般,轮子不如 React 和 Vue 丰富。

我们继续看代码。

以左侧栏 LeftPanel 为例,HTML 为:

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第5张图片

这里的 se-button 就是一个 Web Component 组件,里面有局部样式和交互逻辑,类似 React 和 Vue。

全局样式文件是 svgedit.css

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第6张图片

LeftPanel 类初始化后会调用 init 方法。

该方法会:

  1. 读取前面的 HTML 创建一个 template 元素,然后添加 DOM 树中。

  2. 给一些 DOM 元素绑定了事件响应函数。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第7张图片

$id 这些是工具类方法。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第8张图片

下面代码的作用是,给选择工具按钮绑定方法,该方法更改编辑器的模式为选择模式。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第9张图片

LeftPanel 的 init 方法是在 EditorStartUp 类(这个是 Editor 的父类)的 init 方法中被调用的。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第10张图片

图形拾取

点选 图形的图形拾取是交给浏览器,监听鼠标按下事件的方式,读取 mouseEvent.target

因为可能有组的存在,所以会不断地读 parentNode 找最顶层的 group 元素,直到当前 group 结束。

框选 会在点中空白区域出现,并将当前模式(currentMode)临时切换为 multiselect。

期间产生的选区矩形元素保存在 svgCanvas.rubberBox 属性中。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第11张图片

拖拽修改选区矩形宽高时,会递归 SVG 树,计算它们的 bbox,判断是否和选区矩形相交。将相交的图形放到 selectedElements 属性中。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第12张图片

工具管理

切换工具使用 SvgCanvas.setMode('line') 的方式。

不同工具都有各自实现的事件响应函数,当用户进行鼠标操作时,会执行 mouseDownEvent、mouseMoveEvent、mouseUpEvent,会根据 mode 执行不同的工具的方法。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第13张图片

钢笔工具对应 svgCanvas.pathActions 属性,对应 pathActionsMethod 方法,没有用类,而是用了闭包的方式进行逻辑封装。

图形工具的逻辑倒没有抽一个对象出来,直接写在 ouseDownEvent、mouseMoveEvent、mouseUpEvent 里。

操作的历史记录

我以前的文章说过,历史记录需要维护一个撤销栈和一个重做栈。

两个栈等价于一个数组或双向链表中,加上一个指针,该指针指向多个命令中的当前命令。

撤销就是把指向往左移动,重做往右移,新操作则把指针后面的命令丢掉,然后把这个新的操作加到数组中,并将指针后移。

SVGEdit 的历史命令都保存在 UndoManager 类的 undoStack 数组中,并用 undoStackPointer 指针指向最新命令的位置。

SVGEdit 使用了 patch(打补丁)的方式记录历史操作,没有使用图形树快照的方式

下面是移动一个矩形产生的操作命令,它记录了修改图形属性的命令,记录了一个元素修改前后的属性。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第14张图片

这里有个特殊的 BatchCommand 批量命令对象,它的 stack 数组记录了一次性要操作的子命令。

其实就是 宏命令。宏命令的作用是将多个命令组合在一起批量执行,以实现一步执行多个操作。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第15张图片

各种命令类保存在 svgCanvas.history 中。

老牌开源 SVG 编辑器 SVGEdit 是如何架构的?_第16张图片

简评

UI 框架应该选择 React 或 Vue

这样项目才会有更多人使用,作为一款比较古早的编辑器才可能焕发第二春。

最好是 Vue3,国内很多中小厂在用,那里的程序员不喜欢造轮子,这样他们就会用你这个编辑器,然后社区繁荣,赢。当然最好 React 和 Vue 都做。

SVGEdit 丢掉 jQuery 用 Web Component,我不是很理解,外国比较流行这个?这样很难集成进公司的项目中,不利于发展。

不要使用单例

我看不少地方用了单例,单例模式有个问题,如果页面需要同时有多个编辑器实例,比如做两张图纸对比功能。

那它们就会因为单例的对象共享导致冲突,比如改了编辑器 A 的设置属性会同时改了编辑器 B 的,这不是我们想要的。

类的面向对象风格是比较好的解法,每个对象都要创建一个新的实例,就不会冲突了。

较多的 UI bug

选中一个元素就能复现,此外 UI 也不好看。

监听鼠标事件应该放到 document 下

放到 SVG 的容器或 SVG 上其实并不是很好的做法,当光标移到这些元素外时,监听就消失了,绑定到 doucment 下即使光标移动到浏览器外都能监听。

结尾

SVGEdit 支持的功能很多,但 UI 比较复古,小 bug 有点多的样子。

但如果你要做 SVG 编辑器,与其从零开始,不如基于 SVGEdit 做去二次开发。

我是前端西瓜哥,关注我,学习更多图形编辑器知识。

你可能感兴趣的:(开源,编辑器,架构)