阅读 159

基于Web的svg编辑器(2)——层次结构设计(DOM结构)

svg 编辑器系列(2)其实在之前已经写过了,但写得不好,所以这次重写一下,顺便也把示例代码重写了。

本文主要讲解一款 svg 编辑器的DOM结构,该如何分层以及这样分层的原因。DOM 结构主要参考了一款名为 svgedit的开源 svg 编辑器。

代码实现

代码用到了 svgjs 库。不过应该还是挺好懂的。

演示链接:f-star.github.io/web-editor-…

源码地址:github.com/F-star/web-…

网页截图:

总览

DOM 结构如下:

svg 编辑器
├── workarea (视口层)
    └── svgcanvas (挂载层)
        └── svgRoot (最上层的svg)
            ├── canvasBg (svg背景层)
            ├── svgcontent (绘制层)
            |   ├── layout1 (图层一)
            |   ├── layout2 (图层二)
            |   └── ...
            ├── guideLine (辅助线层)
复制代码

视口层

div#workarea

顾名思义,该层为编辑器的可视范围。当 svgRoot 的高或宽大于该层的宽高时,会显示滚动条。

挂载层

div#svgcanvas

挂载层负责 svg 的挂载。它会保持宽高和 svgRoot 相同。你可能会奇怪这层到底有什么用,直接挂载到适口层不也行吗?没错,直接挂载在视口层也是可行的,但软件设计需要考虑到一些可能会发生的情况。这里我们加上了这层,是考虑到后期我们可能会需要在 svgRoot 的同一层上添加一些 div 元素(毕竟svg中是无法添加 div 元素的),比如说添加一些备忘录。

svgRoot

svg#svgRoot

该元素为 根部svg 元素,它并不是真正绘制 svg 的载体,真正绘制 svg 图形的地方是它的内嵌 svg 元素,即绘制层。我们追加这层根部 svg 的目的,是为了使超出内嵌 svg 范围外的矢量图形,仍然能显示出来(svgRoot 比)。如图所示:

这里给个图片

svg背景层

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。则有:

  • svgroot 的宽为 w * 3, 高为 h * 3;
  • canvasbg 的宽为 w,高为 h;
  • workarea 的 scrollLeft 为 (w * 3 - workarea.width) / 2,(w * 3 - workarea.height) / 2。(使画布位于视口层的正中心)

最后我们用代码绘制一个 path,并调用写好的 showSelectedBox() 方法,显示它的选中框。

结尾

这篇文章主要讲述了 svg 编辑器的层次结构(DOM结构)设计和这样设计的原因,并简单讲述了 svg 编辑器初始化时需要做的事情。

下一篇系列文章的内容应该是讲解如何进行工具(如选择工具切换为钢笔工具)的激活和切换,以及如何配合事件响应函数实现其中一个简单的工具功能。

关注下面的标签,发现更多相似文章
评论