SVG路径元素完全解析:直线命令入门指南

本文深入解析SVG path元素的直线绘制命令,包括M、L、H、V、Z等关键指令的使用方法和语法规则,通过可视化示例展示如何用path替代line/polyline/polygon元素,并介绍复合路径和相对命令的实现技巧。

解码SVG路径元素:直线命令

SVG很简单——直到你遇到path元素。但它并不像初看起来那么令人困惑。在这系列文章的第一部分中,Myriam Frisano旨在教你<path>的基础知识及其有时令人费解的命令。通过简单示例和可视化,她将帮助你理解SVG最强大元素的简单语法和基本规则,最终使你能够将SVG语义标签转换为path能理解的语言。

在之前的文章中,我们看了一些手动编写SVG代码的实用示例。在该指南中,我们涵盖了SVG元素rect、circle、ellipse、line、polyline和polygon(以及g)的基础知识。

这次我们将处理一个更高级的主题,SVG元素的绝对主力:path。别误会;我仍然坚持我的观点,图像路径最好在矢量程序中绘制而不是编码(除非你是那种用代码创作非逻辑视觉艺术的创意人士——那就去创造令人惊叹的奇迹吧;你可能不是本文的目标受众)。但当涉及技术绘图和数据可视化时,path元素解锁了广泛的可能性,并打开了手动编码SVG的世界。

path语法可能非常复杂。我们将分两部分来处理。在第一部分中,我们将学习所有关于直线和角路径的知识。在第二部分中,我们将使线条弯曲、扭转和转向。

所需知识和指南结构

注意:如果你不熟悉SVG的基础知识,例如viewBox的主题和简单元素(rect、line、g等)的基本语法,我建议在深入阅读本文之前先阅读我的指南。如果你想理解示例中的每一行代码,还应该熟悉<text>

在我们开始之前,我想快速回顾一下我如何使用JavaScript编写SVG代码。我不喜欢处理数字和数学,阅读每个属性都填满数字的SVG代码让我完全无法理解。通过给坐标命名并使所有数学易于解析和写出,我对这类代码的处理得心应手得多,我认为你也会这样。

本文的目标更多是关于理解path语法,而不是关于如何进行布局或如何利用循环和其他更基本的东西。因此,我不会带你完成每个示例的完整设置。相反,我会分享代码片段,但它们可能从CodePen稍作调整或简化,以使本文更易读。但是,如果有关代码的具体问题不在CodePen演示的文本中,评论部分是开放的。

为了保持所有框架无关,代码是用原生JavaScript编写的(不过,实际上,TypeScript是你的朋友,你的SVG越复杂,我在编写其中一些代码时错过了它)。

为成功设置

由于path元素依赖于我们对插入命令的一些坐标的理解,我认为如果我们有一些视觉导向会容易得多。因此,所有示例都将编码在传统viewBox设置的可视化表示之上,原点位于左上角(因此,值的形状为0 0 ${width} ${height})。

我还添加了文本标签,以便更容易指向网格内的特定区域。

请注意,如果你希望文本可访问,我建议在SVG中的<text>元素内添加文本时要小心。如果图形像网站的其余部分一样依赖文本缩放,最好通过HTML呈现。但对于我们这里的示例,应该足够了。

所以,这是我们将要绘制的内容:

参见CodePen:SVG Viewbox Grid Visual [forked] by Myriam。

好了,我们现在有一个可视化网格的ViewBox。我认为我们已经准备好与这个野兽进行第一次会话了。

进入path和全能的d属性

<path>元素有一个d属性,它说自己的语言。因此,在d内部,你是在用“命令”说话。

当我想到非path与path元素时,我喜欢认为我们必须编写更复杂的绘制指令的原因是:所有非path元素都只是更笨的路径。在后台,它们有一个预绘制的路径形状,它们总是根据你传入的几个参数来渲染。但path没有默认形状。形状逻辑必须暴露给你,而对于所有其他元素,它可以整齐地隐藏起来。

让我们学习这些命令。

一切开始的地方:M

第一个是每个路径开始的地方,是M命令,它将笔移动到某个点。此命令放置你的起点,但它不绘制任何东西。仅带有M命令的路径在清理SVG文件时会自动删除。

它需要两个参数:起始位置的x和y坐标。

1
const uselessPathCommand = `M${start.x} ${start.y}`;

基本线条命令:M、L、H、V

这些有趣且简单:L、H和V都从当前点绘制一条线到指定点。

L需要两个参数,你要绘制到的点的x和y位置。

1
const pathCommandL = `M${start.x} ${start.y} L${end.x} ${end.y}`;

另一方面,H和V只接受一个参数,因为它们只在一个方向上绘制一条线。对于H,你指定x位置,对于V,你指定y位置。另一个值是隐含的。

1
2
const pathCommandH = `M${start.x} ${start.y} H${end.x}`;
const pathCommandV = `M${start.x} ${start.y} V${end.y}`;

为了可视化这是如何工作的,我创建了一个绘制路径的函数,以及带有标签的点,这样我们可以看到发生了什么。

参见CodePen:Simple Lines with path [forked] by Myriam。

该图像中有三条线。L命令用于红色路径。它从M在(10,10)开始,然后对角线向下移动到(100,100)。命令是:M10 10 L100 100。

蓝线是水平的。它从(10,55)开始,应该结束于(100,55)。我们可以使用L命令,但我们必须再次写入55。所以,相反,我们写入M10 55 H100,然后SVG知道回顾M的y值作为H的y值。

绿线也是一样,但当我们使用V命令时,SVG知道回顾M的x值作为V的x值。

如果我们将结果水平路径与<line>元素中的相同实现进行比较,我们可能会注意到path可以多么高效,并且对于任何不说path语言的人来说,移除了相当多的含义。

因为,当我们看这些字符串时,其中一个被称为“line”。虽然其余部分在上下文之外没有任何意义,但line肯定会在我们脑海中唤起特定的图像。

1
2
<path d="M 10 55 H 100" />
<line x1="10" y1="55" x2="100" y2="55" />

用Z制作多边形和多段线

在上一节中,我们学习了path如何像<line>一样行为,这很酷。但它可以做更多。它也可以像polyline和polygon一样行为。

记住,这两个基本上工作方式相同,但polygon连接第一个和最后一个点,而polyline不连接?path元素可以做同样的事情。有一个单独的命令用一条线闭合路径,即Z命令。

1
2
const polyline2Points = `M${start.x} ${start.y} L${p1.x} ${p1.y} L${p2.x} ${p2.y}`;
const polygon2Points  = `M${start.x} ${start.y} L${p1.x} ${p1.y} L${p2.x} ${p2.y} Z`;

所以,让我们看看这个动作,并创建一个重复的三角形形状。每次奇数时,它是开放的,每次偶数时,它是闭合的。非常整洁!

参见CodePen:Alternating Triangles [forked] by Myriam。

当比较path与polygon和polyline时,其他标签告诉我们它们的名称,但我认为知道多边形是什么的人比知道线是什么的人少(可能甚至更少的人知道多段线是什么。哎呀,甚至我写这篇文章的程序都告诉我polyline不是一个有效的词)。在我看来,为了可读性而使用这两个标签而不是path的论点是薄弱的,我猜你可能会同意这看起来像是给SVG元素的同等水平的无意义字符串。

1
2
3
4
5
<path d="M0 0 L86.6 50 L0 100 Z" />
<polygon points="0,0 86.6,50 0,100" />

<path d="M0 0 L86.6 50 L0 100" />
<polyline points="0,0 86.6,50 0,100" />

相对命令:m、l、h、v

所有线条命令都存在绝对和相对版本。区别在于相对命令是小写的,例如m、l、h和v。相对命令总是相对于最后一个点,因此不是声明x值,而是声明dx值,说你要移动多少单位。

在我们视觉上看示例之前,我希望你看看以下三个线条命令。尽量不要事先看CodePen。

1
2
3
4
5
const lines = [
  { d: `M10 10 L 10 30 L 30 30`, color: "var(--_red)" },
  { d: `M40 10 l 0 20 l 20 0`, color: "var(--_blue)" },
  { d: `M70 10 l 0 20 L 90 30`, color: "var(--_green)" }
];

正如我提到的,我讨厌看没有意义的数字,但有一个数字在大多数上下文中含义相当恒定:0。看到0与我刚学会的命令结合意味着相对立即告诉我什么都没有发生。单独看到l 0 20告诉我这条线只沿一个轴移动而不是两个。

看整个蓝色路径命令,重复的20值让我感觉到形状可能有一些规律性。第一个路径通过重复10和30做到了一点。但第三个?作为一个不能在脑子里做数学的人,第三个字符串什么也没给我。

现在,你可能会惊讶,但它们都绘制相同的形状,只是在不同的地方。

参见CodePen:SVG Compound Paths [forked] by Myriam。

那么,我们能识别蓝色路径中的规律性有多大价值?在我看来,不是很大。在某些情况下,使用相对值比绝对值更容易。在其他情况下,绝对值是王道。没有一个比另一个更好或更差。

而且,在所有情况下,前面的示例如果用一个变量表示间隙,一个变量表示形状大小,以及一个生成路径定义的函数设置会更高效,该函数在循环内调用,以便它可以接收索引来正确计算起点。

跳跃点:如何制作复合路径

另一个非常有用的东西是你在前面的CodePen中看不到的,但它与网格及其代码有关。

我偷偷加入了一个网格绘制更新。

使用早期示例中使用的方法,使用line绘制网格,上面的CodePen会渲染出14个单独的元素。如果你去检查最后一个CodePen的最终代码,你会注意到在.grid组中只有一个path元素。

它看起来像这样,看起来并不有趣,但隐藏了如何可能的秘密:

1
<path d="M0 0 H110 M0 10 H110 M0 20 H110 M0 30 H110 M0 0 V45 M10 0 V45 M20 0 V45 M30 0 V45 M40 0 V45 M50 0 V45 M60 0 V45 M70 0 V45 M80 0 V45 M90 0 V45" stroke="currentColor" stroke-width="0.2" fill="none"></path>

如果我们仔细看,我们可能会注意到有多个M命令。这是复合路径的魔力。

由于M/m命令实际上不绘制而只是放置光标,路径可以有跳跃。

因此,每当我们有多个共享共同样式且不需要有单独交互的路径时,我们可以将它们链接在一起以使代码更短。

接下来

有了这些知识,我们现在能够用path命令替换line、polyline和polygon,并将它们组合在复合路径中。但还有更多要发现,因为path不仅提供线条的外语版本,还给我们选项来编码具有开放空间的圆和椭圆,有时也可以弯曲、扭转和转向。我们将把这些称为曲线和弧,并在下一篇文章中更明确地讨论它们。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计