Web编程技术营地
研究、演示、创新

SVGEditor Test

撰写时间:2024-07-06

修订时间:2026-01-07

由于输出面板中只有一个svg元素,因此要求其大小必须精准到像素级别。

SVG默认宽高值

输出面板高度值默认为150px。此外,SVGEditor使用了以下默认设置:

svg { display: block; box-sizing: border-box; border: 1px solid gray; width: 100%; height: 100vh; }

box-sizing设置为border-box,可让我们在设置svg的宽高值时,不会超出输出面板的范围而出现滚动条,当我们扫动 Magic Mouse 时,不会出现晃动的感觉。

height设置为100vh,可在不为svg元素指定height属性值、或未通过CSS设置height值、或未将result-div-height的值指定为auto时,均能自动铺满整个输出面板。

未设置宽高值

直接编写代码,无需考虑具体的宽、高值。

输出:

输出面板的区域为视口区域(这里的视口指HTML中的视口,而非SVGviewport)。视口宽度自动拉伸,视口高度为固定的150px

svg的宽、高自动铺满整个视口。

SVG的内容超出输出面板的范围

默认情况下,SVG使用viewport (视口) 机制来建立起两套坐标系,因此当子元素的内容超出视口范围时,它不会出现滚动条。

下面的代码绘制了一个宽度值为1500px的矩形,显然,在大多数情况下我们都无法看到其全貌。

根据SVGviewport的特点,只需通过viewBox建立起一个范围足够包含所有子元素内容的viewport,就能自动地在HTML视口中等比缩放并显示所有的内容。

编写代码:

效果:

因为未为svg显式地指定宽度、高度值,因此svg的宽度能动态地撑开整个输出面板的宽度,其高度取svg的默认高度150px。这是目标坐标系 (target coordinates)。

svg只有一个rect子元素,其高度未超出svg的默认高度值150px,因此viewBox所建立起的viewport的高度值取150px即可。rect的宽度值为1500px,则viewBox所建立起的viewport的宽度值取值比其稍大一点,以在svg的目标坐标系的右边留出一些空白,视觉上更好看一些。

通过viewBox所建立起的viewport,称为源坐标系source coordinates)。这样,SVG自动帮我们将超大的内容按比例缩放至整个输出面板中。

这种方式的好处是,在svg150px默认高度值内,我们可以随心所欲地编写代码,而svg的内容自动吸附输出面板,不会因内容太多而出现烦人的滚动条。运行代码后,根据效果来调节viewBox即可。

改变默认宽高值

通过CSS设置宽高值

又分为两种情况。第一种情况,只设置值。

svg { border: 1px solid gray; width: 300px; height: 300px; }

这种情况不会改变输出面板的高度。如果svg的高度值超出输出面板的高度,输出面板将出现滚动条。

第二种情况,当希望改变输出面板的高度时,可将SVGEditorresult-div-height属性值设置为auto

...

则:

svg { border: 1px solid gray; width: 300px; height: 350px; }

CSS中的height值可少于svg默认高度值150px

注:bodyline-height会影响自动高度的精准计算,从而导致svg的下方会多出一点点的留白。由于该属性又是被各种子元素自动继承的属性,因此,在父类LiveEditor中,只将body自身的值初始化为0px,子元素则取默认值:

body { line-height: 0; * { line-height: initial; } }

设置SVG元素的内联宽高值

有时需要为svg指定内联的宽高值,从而无需额外的CSS设置。

...

此时,情况变为较为复杂,主要涉及到:

  1. iframe与输出面板的高度如何相互适应
  2. CSS属性值的继承与覆盖

为此,LiveEditor引入了新的钩子方法doAfterApplyHTMLFromTabSVGEditor调用其以进行进一步的设置。

当有任一svg元素设置了内联的宽高值时,将取消原有的自动铺满整个输出面板的设置,但为每个svg保留了block及边框的设置。

doAfterApplyHTMLFromTab(doc, customTabTitleContentMap) { let svgSrc = customTabTitleContentMap['SVG']; doc.body.insertAdjacentHTML('afterbegin', svgSrc); let oneSvg = doc.querySelector('svg'); if (oneSvg.hasAttribute('width') || oneSvg.hasAttribute('height')) { let svgDefaultStyle = doc.head.querySelector('link[href*="svg-default-style.css"]'); svgDefaultStyle?.remove(); let svgs = doc.querySelectorAll('svg'); svgs.forEach(svg => { svg.style.display = 'block'; svg.style.boxSizing = 'border-box'; svg.style.border = '1px solid gray'; }); } }

则有以下效果:

这种情况,不会改变输出面板的高度。

此时如果result-div-height的值为auto,则高度值优先取自CSS所设置的高度值;若CSS无相应的设置高度的值,再取自svg所声明的height值。这两种情况,都会钳制输出面板的高度。

svg { border: 1px solid gray; height: 200px; }

较好的方式

综合以上各种方法,应用自动高度功能,再加上preserveAspectRatio,可取得非常理想的效果。

输出:

上面作法兼顾了用户体验及编写代码的便捷,具体优势为,无需额外指定CSS,用户可自行决定输出面板的宽度及高度,输出结果自动按比率缩放后在输出面板居中;编写代码只需在viewBox所指定的范围内编写即可。

若将svgwidth设置为具体的数值,则可得到空间紧凑、且有清晰边框的图像。

当设置了viewBox属性值后,可以仅设置widthheight其中的一个。

甚至widthheight属性值一概不予指定。

这种方式,宽度将自动铺满整个输出面板,且按viewBox所指定的比例确定高度。其优点是只需指定viewBox而无需指定widthheight,要编写的代码量变少了。但其缺点是,viewBox的坐标系只能限定在较小范围内,否则输出面板的高度将因太大而不好看;且一旦修改viewBox的设置值,svg各个子元素的代码也需随之修改。

综合考虑以上几种方式的利弊,最便利的方式是:

同时设置viewBoxpreserveAspectRatio属性值,先保证代码比例缩放在输出面板中;然后再根据需要,仅设置widthheight其中的一个值,或者同时设置。

SVGGrids

svg-editor内置支持背景网格。

SVGGrids
new SVGGrids('my-svg', 100, true);

JavaScript面板中调用:

new SVGGrids('my-svg', 100, true);

即可。第1个参数为svgid。第2个参数为长刻度标尺的间距(将根据此间距自动创建值为一半、但无标签的短刻度)。第3个参数表示是否绘制背景网格。待编写完SVG所有内容后,将此参数改为false,则可得到一份无背景网格、干净的图像。

SVGGrid支持viewBox属性。

new SVGGrids('my-svg', 100, true);

两个问题。一是为何标签字体较小;二是在桌面系统中,左右有留白。

主要原因是,通过viewBox所指定的源坐标系过大,而通过widthheight所指定的目标坐标系过小所导致。为了要保持源坐标系的比例,有效区域只能取widthheight的最小值(SVG引擎自动计算的结果)。

解决的方法有二。

第一种解决方法是缩小源坐标系。但这会导致两个问题。

1、右下角的圆位于源坐标系的边缘,若缩小源坐标系,则该圆将被裁剪掉。

2、若仍要缩小源坐标系,则必须同步调整SVG各个子元素的坐标值。这较麻烦。并且,有时出于研究的需要,我们会将第三方的源代码粘帖进SVGEditor中以查看运行效果、调试其代码,如果该源代码较多时,一一修正各个子元素的坐标无疑是场噩梦。

因此这种方式比较笨拙。

第二种解决方法是,因上面设置了按比例居中缩放的方式,因此,左右出现留白,说明目标坐标系的水平空间很充裕,但高度不够。此时可适当加大height属性值。本例中若将height值改为标尺刻度最大值700后再运行,则上下左右均不会出现留白,这是最理想的缩放效果,字体将变大而醒目。

调整后若上下出现留白,说明height值调得过大了,将其值适当调小即可。