原文标题:如何使用基本形状简化 SVG 代码 / How to Simplify SVG Code Using Basic Shapes
发布时间:Sep 3, 2020
文章作者:Mariana Beldi
文章译者:腐烂的橘子
原文链接:https://css-tricks.com/how-to-simplify-svg-code-using-basic-shapes/
本文已授权
处理图标有很多方法,但是在一些好的解决方案当中,总是可以看到 SVG 的影子,主要是因为 SVG 是在代码中绘制的,所以在上下文中具有良好的伸缩性、适应性和灵活性。
但我们同时发现,SVG 但代码往往包含了一些不必要的代码,使之看起来冗长和多余,导致文件内容滚动浏览时很不方便。
我们可以使用这两篇文章中的解决方案来处理这个问题:
对于服务端,我们可以 使用 PHP(或类似的东西) 来提取文件中的内容,而不是直接使用。
试想,如果我们着眼于图标本身来解决这个问题,而不是在代码层面解决,效果会不会更好呢?因此针对以上问题,我想使用基本形状,以及更少的代码量来制作相同的图形。这样一来,我们在不牺牲图片质量和外观的条件下,可以在项目中获得更小、更可控制的语义化图片。接下来,我想通过不同的示例来探讨常用图标的代码,以及如何使用一些最简单的 SVG 形状重绘它们。
这是我们要处理的图标:

接下来,我们看一些详细的示例。
这是在 holasvg.com上创建的简单图标的列表,在阅读完本文之后,你将知道如何修改和使用它们。
这是从 flaticon.com 上下载的并由 pixel-perfect 构建的「关闭」或「十字」图标的代码:
<svg xmlns="http://www.w3.org/2000/svg" height="329pt" viewBox="0 0 329.26933 329" width="329pt"><path d="m194.800781 164.769531 128.210938-128.214843c8.34375-8.339844 8.34375-21.824219 0-30.164063-8.339844-8.339844-21.824219-8.339844-30.164063 0l-128.214844 128.214844-128.210937-128.214844c-8.34375-8.339844-21.824219-8.339844-30.164063 0-8.34375 8.339844-8.34375 21.824219 0 30.164063l128.210938 128.214843-128.210938 128.214844c-8.34375 8.339844-8.34375 21.824219 0 30.164063 4.15625 4.160156 9.621094 6.25 15.082032 6.25 5.460937 0 10.921875-2.089844 15.082031-6.25l128.210937-128.214844 128.214844 128.214844c4.160156 4.160156 9.621094 6.25 15.082032 6.25 5.460937 0 10.921874-2.089844 15.082031-6.25 8.34375-8.339844 8.34375-21.824219 0-30.164063zm0 0"/></svg>结果:

在此示例中,一切都发生在 <path> 的 data 属性 d 中,其中包含了很多命令和相关参数。SVG 所做的就是绘制其边界的形状。

以上演示采用 move.io 构建。
如果你会使用 Illustrator,这其实等效于绘制两条互相垂直的线,改变它们的形状之后组合成一个复合形状。

我们可以使用 <path> 元素绘制复杂的形状,但是在当前情况下,我们可以创建两条具有相同外观的线:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50" height="50" overflow="visible" stroke="black" stroke-width="10" stroke-linecap="round">
<line x1="0" y1="0" x2="50" y2="50" />
<line x1="50" y1="0" x2="0" y2="50" />
</svg>具体说来,首先,我们在 viewBox 中定义一条直线 a,它的坐标从 0, 0 到 50, 50,当然你可以自己定义尺寸,SVG 试中可以很好地缩放到你定义的任何高度和宽度。除此之外,我们还定义了 50 个单位的内联高度和宽度,避免图形进行额外的计算。
使用 <line> 元素,我们只需声明两个端点的坐标即可。在这个例子中,第一条线的起始点为 x = 0 y = 0,终点为 x = 50 y = 50。

在代码中是这样婶儿的:
<line x1="0" y1="0" x2="50" y2="50" />第二条线的起始点是 x = 50 y = 0,终点为 x = 0 y = 50:
<line x1="50" y1="0" x2="0" y2="50" />SVG 画出来的线默认是没有颜色的,所以我们设置了 stroke="black",除此之外,SVG 标签中,我们还设置了 stroke-width="10" 来指定了 10 个单位的宽度,stroke-linecap="round" 来指定圆角。这些属性都继承于 <svg> 标签中。
<svg ... stroke="black" stroke-width="10" stroke-linecap="round" ...>现在由于直线比默认值(1 个单位)大了 10 个单位,因此直线可能会被 viewBox 裁剪。我们可以将端点向内移动 10 个单位并且添加样式 overflow=visible。
同时,我们可以删除等于 0 的值,因为默认值为 0。这意味着这两行最后只有两行非常少的代码:
<line x2="50" y2="50" />
<line x1="50" y2="50" />以上,我们仅仅通过将 <path> 元素修改为 <line> 元素,不仅制作了一个较小的 SVG 文件,而且维护了一个更具语义性、更可控的代码块,从而使以后的维护变得更加容易,且视觉效果与原始的效果完全相同。
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" overflow="visible" stroke="black" stroke-width="10" stroke-linecap="round">
<line x2="50" y2="50" />
<line x1="50" y2="50" />
</svg>结果:

(同样的结果,不同的代码)
我们以 barracuda 在 The Noun Project 的项目中创建的时钟图标为例:
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" x="0px" y="0px" viewBox="0 0 100 125"><g transform="translate(0,-952.36218)"><path style="text-indent:0;text-transform:none;direction:ltr;block-progression:tb;baseline-shift:baseline;color:#000000;enable-background:accumulate;" d="m 50,962.36215 c -22.0558,0 -40,17.94416 -40,39.99995 0,22.0558 17.9442,40.0001 40,40.0001 22.0558,0 40,-17.9443 40,-40.0001 0,-22.05579 -17.9442,-39.99995 -40,-39.99995 z m 0,6 c 18.8132,0 34,15.18686 34,33.99995 0,18.8132 -15.1868,34.0001 -34,34.0001 -18.8132,0 -34,-15.1869 -34,-34.0001 0,-18.81309 15.1868,-33.99995 34,-33.99995 z m 0,5 c -1.6568,0 -3,1.34316 -3,2.99996 l 0,25.99999 c 0,1.6569 1.3432,3 3,3 l 24,0 c 1.6568,0 3,-1.3431 3,-3 0,-1.6568 -1.3432,-2.99999 -3,-2.99999 l -21,0 0,-23 c 0,-1.6568 -1.3431,-2.99996 -3,-2.99996 z" fill="#000000" fill-opacity="1" stroke="none" marker="none" visibility="visible" display="inline" overflow="visible"/></g></svg>结果:

这个形状同样也是用
同样,我们使用更加简单的命令从头开始重新绘制此图形,首先设置 viewBox 为 0, 0 到 100, 100,并且宽度和高度与这些单位所匹配。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100" fill="none" stroke="black" stroke-width="10" stroke-linecap="round" stroke-linejoin="round">
<circle cx="50" cy="50" r="40"/>
<path d="M50 25V50 H75" />
</svg><svg> 标签中的样式继续保持与前面叉号的样式相同,其中 fill="black" 是默认设置,我们需要将其设置为 "none",否则时钟将会以黑色填充,从而遮盖了指针的形状。
<circle> 中我们需要指出圆心坐标(cx 和 cy 值)和半径大小(r 值)。在此示例中,指针的半径要略小于 viewBox,因此当指针为 10 个单位宽时就不会互相遮挡了。
查看 Chris Coyier 的 插图指南,以获取有关 SVG 语法的入门知识。
我们可以使用 <path> 来绘制时钟的指针,因为它有一些非常简单的命令。设置 <path> 中的参数 d(数据)时,先设置 M(移动),后面紧跟的是开始坐标,这里是 50, 25,即圆的中心坐标。
然后,V 代表 vertical,即垂直;H 代表 horizontal,即水平。这两个字母后紧跟一个值,表示长度,正负表示方向,原理与二维的笛卡尔坐标非常类似。在此实例中,我们设置了 V50 H75,即位于第一坐标系。注意,这里大写字母后的数字代表绝对位置,即指针的长度均为 25,而不是 50 和 75。如果使用小写字母,则数字表示在一个方向上移动的单位数量,而不是坐标系中的绝对点。
简化后的代码如下:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100" fill="none" stroke="black" stroke-width="10" stroke-linecap="round" stroke-linejoin="round">
<circle cx="50" cy="50" r="40"/>
<path d="M50 25V50 H75" />
</svg>结果:

这是在 Illustrator 中绘制的信封图标,且没有修改原始形状。导出后的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 310 190" style="enable-background:new 0 0 310 190;" xml:space="preserve">
<style type="text/css">
.st0 {
fill: none;
stroke: #000000;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
</style>
<rect x="5" y="5" class="st0" width="300" height="180" />
<polyline class="st0" points="5,5 155,110 305,5 " />
</svg>结果:

Illustrator 在导出图形时提供了一些 SVG 选项。我在「CSS属性」下拉列表中选择了「样式元素」,因此我可以使用
<style>标签,其中包含一些可能要移至 CSS 文件的类。
下面这段代码中已经具备了基本的形状!我们没有在 Illustrator 中选择「形状到路径」选项,除此之外,我们还可以使用 SVGOMG 对其进行进一步优化,以删除注释,XML 指令和不必要的数据,例如空元素。其他功能如果想要删除的话也可以手动删除。
我们已经有了一些简洁的东西:
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 310 190" xml:space="preserve">
<style>.st0{fill:none;stroke:#000;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10}
</style><rect x="5" y="5" class="st0" width="300" height="180"/>
<polyline class="st0" points="5 5 155 110 305 5"/>
</svg>我们可以移除下面的代码而不会影响信封的外观,主要包括:
version="1.1"(自 SVG2 开始 不推荐使用)id="Layer_1" (没用)x="0" (默认值,可删除)y="0" (默认值,可删除)xml:space="preserve"(自 SVG2 开始 不推荐使用)<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 310 190">
<style>.st0{fill:none;stroke:#000;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10}
</style>
<rect x="5" y="5" class="st0" width="300" height="180"/>
<polyline class="st0" points="5 5 155 110 305 5"/>
</svg>如果想要再更近一步,我们可以将 CSS 样式移到单独的文件中。
<rect> 标签需要设置起点,因此我们将左上角的 x="5" y="5" 作为起点开始绘制。首先,我们创建一个宽度为 300,高度为 180 的矩形。与时钟类似,因为我们有 10 个单位的宽度,因此如果我们从 0, 0 开始绘制矩形,则会有干涉。
<polyline> 与 <line> 相似,它在 points 属性内定义了无数个点,就像坐标对一样,一个接一个,第一个数字代表 x,第二个数字代表 y。每个数值之间用逗号隔开,或者将逗号替换为空格也可以。
最终,我们得到了如下简化的代码:
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 310 190">
<style>
.st0 {
fill: none;
stroke: #000;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10
}
</style>
<rect x="5" y="5" class="st0" width="300" height="180" />
<polyline class="st0" points="5 5 155 110 305 5" />
</svg>结果:

此外还有 <polygon> 和 <ellipse>,这里暂时不提供简化 SVG 的实例了,仅介绍快速使用它们的方法。
<polygon> 与 <polyline> 相同,不同的是此元素将定义的是闭合的多边形形状。这是 MDN 的示例:
<!-- Learn about this code on MDN: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon -->
<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<!-- Example of a polygon with the default fill -->
<polygon points="0,100 50,25 50,75 100,0" />
<!-- Example of the same polygon shape with stroke and no fill -->
<polygon points="100,100 150,25 150,75 200,0" fill="none" stroke="black" />
</svg>
<polyline> 可以绘制一个椭圆,还记得我们之前为时钟图标绘制的圆吗?我们只需要用 rx 和 ry 来替换半径值 r。这是 MDN 的另一个示例:
<!-- Learn about this code on MDN: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse -->
<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<ellipse fill="black" cx="100" cy="50" rx="100" ry="50" />
</svg>
我们在本文中讲到了很多内容,虽然我们使用示例演示了优化 SVG 的过程,但我希望大家能着重掌握以下几点:
<use> 是重构 SVG 代码以及建立自己具有高复用性图标库的好方法。这是在 holasvg.com/icons 上列出的清单,在这里将不断上传更多的图标和功能,现在你已经知道了如何简化 SVG,一起来这里参与创作吧!