svg 编辑器系列(2)其实在以前已经写过了,但写得很差,因此此次重写一下,顺便也把示例代码重写了。git
本文主要讲解一款 svg 编辑器的DOM结构,该如何分层以及这样分层的缘由。DOM 结构主要参考了一款名为 svgedit的开源 svg 编辑器。github
代码用到了 svgjs 库。不过应该仍是挺好懂的。web
演示连接:f-star.github.io/web-editor-…canvas
源码地址:github.com/F-star/web-…bash
网页截图:编辑器
DOM 结构以下:ide
svg 编辑器
├── workarea (视口层)
└── svgcanvas (挂载层)
└── svgRoot (最上层的svg)
├── canvasBg (svg背景层)
├── svgcontent (绘制层)
| ├── layout1 (图层一)
| ├── layout2 (图层二)
| └── ...
├── guideLine (辅助线层)
复制代码
div#workareasvg
顾名思义,该层为编辑器的可视范围。当 svgRoot 的高或宽大于该层的宽高时,会显示滚动条。函数
div#svgcanvas工具
挂载层负责 svg 的挂载。它会保持宽高和 svgRoot 相同。你可能会奇怪这层到底有什么用,直接挂载到适口层不也行吗?没错,直接挂载在视口层也是可行的,但软件设计须要考虑到一些可能会发生的状况。这里咱们加上了这层,是考虑到后期咱们可能会须要在 svgRoot 的同一层上添加一些 div 元素(毕竟svg中是没法添加 div 元素的),好比说添加一些备忘录。
svg#svgRoot
该元素为 根部svg 元素,它并非真正绘制 svg 的载体,真正绘制 svg 图形的地方是它的内嵌 svg 元素,即绘制层。咱们追加这层根部 svg 的目的,是为了使超出内嵌 svg 范围外的矢量图形,仍然能显示出来(svgRoot 比)。如图所示:
这里给个图片
svg#canvasBg
它负责显示出画布的位置,通常设置为白色。
svg#svgcontent
矢量图形真正绘制的地方。导出的 svg 文件内容便是这个元素下的全部内容。注意要设置 overflow="visible"
g#layout
相似 PhotoShop,咱们引入了“图层”的概念。图层就像叠好的一张张半透明(软件中实际上是全透明)的白纸,你能够选择任意一张进行绘画。另外上层的纸的不透明的部分,会挡住它的下层的相同区域的显示。
做为一款编辑器,咱们须要一些辅助线,提供一些选中效果以及一些交互。好比点击一个图形,就显示一个包围着它的矩形,我称之为“选中框”,此外这个矩形上有一些控制点,对它们进行拖拽等操做,能够对当前这个矩形进行缩放或者旋转。为了实现这些功能,辅助线是 svg 编辑器十分重要的部分。
可是为何咱们把辅助线层放到 根部svg 下,而不是直接放到绘制层呢?缘由是在实现画布“缩放”功能的时候,画布的缩放实际上是经过设置 svg#svgcontent 的 viewBox 属性来实现的(后面的svg编辑器系列会详细讲解),这种缩放会致使元素的 stroke-width(线宽)也会跟着变大变小。这样的话,用户体验就很很差了。
放大还好,选中框的线条虽然很大,但被选中的那个元素也好大。当若是是缩小的话,并缩小到很小的时候,就会有一个问题,那就是选中框也变细变小了,这样选中框的一些变形控制点也很差点中了。
辅助线层下的元素通常来讲,在编辑器初始化的时候就应该进行建立,并将其设置 style="display: none;"
。好比说选中框,选中一个元素时,辅助线层下相关的元素一些属性会被修改(如元素的位置),而后显示出来;取消选中时,则将相关元素所有设置为不可见。我不建议直接删除或添加这些元素,由于这会有性能损失。
const svgRoot = SVG('svgcanvas').id('svgroot'); // svg root
const canvasBg = svgRoot.nested().id('canvasBg'); // svg 内容的底色,宽高需和 svg content 同步
const svgContent = svgRoot.nested().id('svgcontent'); // svg 的真正内容位置。
const draw = svgContent.group(); // svgContent 下建立一个 group,做为第一个“图层”(相似ps的图层概念)
const guideLine = svgRoot.group().id('guideLine'); // 放置辅助线的父容器
const selectedBox = guideLine.group().id('selectedBox'); // 选中框相关辅助线
const selectedBoxOutline = selectedBox.polygon() // 选中框-矩形轮廓
.fill('none')
.stroke({width: 1, color: '#4d84ff'})
.hide();
// 选中框的 6个 缩放控制点
const scaleGrips = (() => {
...
})
复制代码
SVG('svgcanvas')
的意思是在 id 为 svgcanvas 的元素下建立一个根部 svg。
// 参数配置
const config = {
bgcolor: '#fff',
contentW: 517,
contentH: 384,
}
// 初始化
svgContent.size(config.contentW, config.contentH).move(config.contentW, config.contentH); // 设置宽高和左上角坐标
canvasBg.size(config.contentW, config.contentH).move(config.contentW, config.contentH);
svgRoot.size(config.contentW * 3, config.contentH * 3);
workarea.scroll( (svgRoot.width() - workarea.w())/2, (svgRoot.height() - workarea.h())/2 ); // 滚动条拖到中间
canvasBg.rect('100%', '100%').fill(config.bgcolor); // canvasBg 添加 白色 rect,实现达到填充背景色效果
复制代码
svgcontent(绘制层)的宽为 w,高为 h。则有:
最后咱们用代码绘制一个 path,并调用写好的 showSelectedBox() 方法,显示它的选中框。
这篇文章主要讲述了 svg 编辑器的层次结构(DOM结构)设计和这样设计的缘由,并简单讲述了 svg 编辑器初始化时须要作的事情。
下一篇系列文章的内容应该是讲解如何进行工具(如选择工具切换为钢笔工具)的激活和切换,以及如何配合事件响应函数实现其中一个简单的工具功能。