SVG 圆分解为路径

已发表: 2022-03-10
快速总结↬本文将向您展示如何将 SVG 圆圈转换为可用于动画和文本路径的路径,以及如何将路径转换为圆圈。 一旦你弄清楚了它是如何工作的,你将能够实现一些非常实用的效果。 让我们深入挖掘。

这篇文章从一个坦白开始:我喜欢手工编码 SVG。 情况并非总是如此,但对于那些不喜欢我的人来说,这似乎很奇怪。 能够手动编写 SVG 有很多好处,例如以工具无法实现的方式优化 SVG(将路径转换为更简单的路径或形状),或者只需了解 D3 或 Greensock 等库的工作方式.

话虽如此,我想更仔细地研究 SVG 中的圆形以及当我们经过一个基本圆形时我们可以用它们做的事情。 为什么是圆圈? 嗯,我喜欢圆圈。 它们是我最喜欢的形状。

首先(希望您之前在 SVG 中看到过一个基本的圆圈),这里有一支笔显示了一个:

参见 Bryan Rasmussen 的钢笔圈。

参见 Bryan Rasmussen 的钢笔圈。

一个圆圈可以做很多事情:它可以是动画的,它可以应用不同的颜色。 不过,在 SVG 1.1 中,有两件非常好的事情不能让圆圈做:不能让另一个图形元素沿着圆圈的路径移动(使用animateMotion元素),并且不能沿着圆圈的路径塑造文本(这将仅在 SVG 2.0 发布后才允许使用)。

跳跃后更多! 继续往下看↓

把我们的圈子变成一条路

有一个小的在线工具可以帮助您创建圆圈外的路径(您可以在此处尝试),但我们将从头开始创建所有内容,以便我们可以了解幕后的真实情况。

为了制作一条圆形路径,我们将实际制作两条弧线,即在一条路径中完成圆的半圆。 您可能已经在上面的 SVG 中注意到,属性CXCYR分别定义了沿 X 和 Y 轴绘制圆的位置,而R定义了圆的半径。 CXCY创建圆心,所以圆是围绕该点绘制的。

复制该圆圈可能如下所示:

 <path d=" M (CX - R), CY a R,R 0 1,0 (R * 2),0 a R,R 0 1,0 -(R * 2),0 " />

注意CX与圆的cx属性相同; CY和圆的cy属性以及圆的Rr属性也是如此。 小a字符用于定义一段椭圆弧。 您可以使用可选的Z (或z )来关闭路径。

小写字母a表示相对于当前位置绘制的椭圆弧的开始——或者在我们的特定情况下:

 <path d=" M 25, 50 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 " />

你可以看到这支笔的神奇之处:

从路径上看 Bryan Rasmussen 的钢笔圈。

从路径上看 Bryan Rasmussen 的钢笔圈。

隐藏在路径下方的是一个带有红色填充的圆圈。 当你玩弄路径的值时,只要路径完全覆盖圆圈,你就会看到那个圆圈(路径本身是一个大小相同的圆圈),我们就会知道我们做对了.

您还应该知道的一件事是,只要您绘制相对弧线,就不需要为您绘制的每条弧线重复a命令。 当您的前 7 个输入为您的弧完成后,接下来的 7 个输入将用于下一个弧。

您可以通过删除路径中的第二个a来尝试使用上面的笔:

 a 25,25 0 1,1 50,0 25,25 0 1,1 -50,0

这可能看起来一样,但我更喜欢把它留在里面,直到我准备好完成一幅画,这也有助于我跟踪我的位置。

这条路径如何运作

首先,我们移动到图像中绝对定位的X,Y坐标。 它不会在那里画任何东西——它只是移动到那里。 请记住,对于圆元素CXCY表示圆的中心; 但正如椭圆弧中发生的那样,弧的真实CXCY将根据该弧的其他属性计算。

换句话说,如果我们希望我们的CX50并且我们的半径是25 ,那么我们需要移动到50 - 25 (当然,如果我们是从左到右绘制的话)。 这意味着我们的第一条弧线是从25 X, 50 Y绘制的,这导致我们的第一条弧线是25,25 0 1,0 50,0

让我们分解弧的值25,25 0 1,0 50,0的实际含义:

  • 25 :圆弧的相对X半径;
  • 25 :圆弧的相对Y半径;
  • 0 1,0 :我不打算讨论三个中间值(旋转、大弧标志和扫描标志属性),因为它们在当前示例的上下文中并不是很重要,只要它们两条弧线相同;
  • 50 :圆弧的终点X坐标(相对);
  • 0 : 圆弧的结束 Y 坐标(相对)。

第二个弧是25,25 0 1,0 -50,0 。 请记住,此弧将从最后一个弧停止绘制的位置开始绘制。 当然,X 和 Y 半径是相同的( 25 ),但结束 X 坐标是当前坐标的-50

显然,这个圆圈可以用许多不同的方式绘制。 这种将圆变成路径的过程称为分解。 在 SVG 2 规范中,圆的分解将使用 4 条弧线完成,但是,它推荐的方法还不能使用,因为它目前依赖于尚未指定的名为分段完成关闭路径的功能。

为了向大家展示我们可以通过多种方式绘制圆圈,我准备了一支带有各种示例的小笔:

请参阅 Bryan Rasmussen 的 Pen all circles。

请参阅 Bryan Rasmussen 的 Pen all circles。

如果您仔细观察,您会看到我们的原始圆圈以及如何在该圆圈顶部绘制路径的五个不同示例。 每条路径都有一个子desc元素,描述使用CXCYR值来构建圆。 第一个示例是我们在此处讨论的示例,而其他三个示例使用了通过阅读代码应该可以理解的变体; 最后一个示例使用四个半圆弧而不是两个,在一定程度上复制了上面链接的 SVG 2 规范中描述的过程。

使用 SVG 的自然 z-indexing 将标记中较晚出现的元素放置在较早出现的元素之上,圆圈相互叠加。

如果你点击笔中的圆形路径,第一次点击会打印出路径是如何构造到控制台的,并给元素添加一个类,这样你就可以看到圆是如何绘制的描边颜色(你可以看到第一个圆圈是用笔划的起始楔形绘制的)。 第二次单击将删除圆圈,因此您可以与下面的圆圈进行交互。

每个圆圈都有不同的填充颜色; 实际的圆形元素是黄色的,并且每当单击它时都会对控制台说“您单击了圆形”。 当然,您也可以简单地阅读代码,因为desc元素非常简单。

从路径到圆

我想你已经注意到了,虽然有很多不同的方法来绘制圆圈,但使用的路径看起来仍然非常相似。 通常——尤其是在绘图程序输出的 SVG 中——圆将由路径表示。 这可能是由于图形程序代码的优化; 一旦你有了绘制路径的代码,你就可以绘制任何东西,所以就使用它。 这可能会导致 SVG 有点臃肿,难以理解。

推荐阅读Sara Soueidan 的“为 Web 创建和导出更好的 SVG 的技巧”

让我们以维基百科中的以下 SVG 为例。 当您查看该文件的代码时,您会发现一旦您通过 Jake Archibald 的 SVGOMG 运行它,就会发现它有很多编辑器! (您可以在此处阅读更多信息),您最终会得到类似以下文件的内容,该文件已经过相当优化,但文档中的圆圈仍呈现为路径:

请参阅 Bryan Rasmussen 的 Pen Wikipedia 螺丝头离合器 A 型。

请参阅 Bryan Rasmussen 的 Pen Wikipedia 螺丝头离合器 A 型。

所以,让我们看看我们是否可以根据我们对路径工作原理的了解,弄清楚如果这些圆圈是实际的圆圈元素,它们应该是什么。 文档中的第一个路径显然不是一个圆圈,而接下来的两个是(仅显示d属性):

 M39 20a19 19 0 1 1-38 0 19 19 0 1 1 38 0z
 M25 20a5 5 0 1 1-10 0 5 5 0 1 1 10 0z

所以记住第二个a可以省略,让我们重写这些以使其更有意义。 (第一条路径是大圆圈。)

 M39 20 a19 19 0 1 1-38 0 a19 19 0 1 1 38 0z

这些弧显然如下:

 aR R 0 1 1 - (R * 2) 0 aR R 0 1 1 (R * 2) 0

这意味着我们的圆半径是19 ,但是我们的CXCY值是多少? 我认为我们的M39实际上是CX + R ,这意味着CX20CY也是20

假设您在所有路径之后添加一个圆圈,如下所示:

 <circle fill="none" stroke-width="1.99975" stroke="red" r="19" cx="20" cy="20" />

你会看到这是正确的,红色的描边圆圈正好覆盖了大圆圈。 重新制定的第二个圆形路径如下所示:

 M25 20 a5 5 0 1 1-10 0 5 5 0 1 1 10 0z

显然,半径是5 ,我敢打赌我们的CXCY值和以前一样: - 20

注意如果CX = 20 ,则CX + R = 25 圆圈位于中心较大的圆圈内,因此显然它应该具有相同的CXCY值。

在路径末尾添加以下圆圈:

 <circle fill="yellow" r="5" cx="20" cy="20" />

您现在可以通过查看以下笔看到这是正确的:

请参阅带有 Bryan Rasmussen 的示例圆圈的 Pen Wikipedia Screw Head Clutch Type A_。

请参阅带有 Bryan Rasmussen 的示例圆圈的 Pen Wikipedia Screw Head Clutch Type A_。

现在我们知道圆圈应该是什么,我们可以删除那些不需要的路径并实际创建圆圈 - 正如您在此处看到的那样:

请参阅由 Bryan Rasmussen 优化的 Pen Wikipedia 螺丝头离合器 A 型。

请参阅由 Bryan Rasmussen 优化的 Pen Wikipedia 螺丝头离合器 A 型。

使用我们的循环路径包装文本

所以现在我们在路径中有了圆圈,我们可以在这些路径上包装文本。 下面是一支与我们之前的“All Circles”笔具有相同路径的笔,但路径上包含了文本。 每当您单击路径时,该路径将被删除,并且文本将被包装在下一个可用路径上,如下所示:

请参阅 Bryan Rasmussen 的 Pen all circles Wrapped Text。

请参阅 Bryan Rasmussen 的 Pen all circles Wrapped Text。

查看不同的路径,您会看到每条路径之间的微小差异(稍后会详细介绍),但首先会看到一些跨浏览器不兼容 - 在第一个路径中尤其明显:

火狐开发者
铬合金
微软边缘

在 Firefox 解决方案中,“Smashing”的起始“S”位于那个有趣的角度的原因是,它实际上是我们开始绘制路径的位置(由于我们使用了 vR 命令)。 这在 Chrome 版本中更为明显,您可以清楚地看到我们绘制的圆圈的第一个饼形楔形:

Chrome 并没有遵循所有的楔形,所以当您将文本更改为“Smashing Magazine”时,就会出现这种结果。

原因是 Chrome 有一个关于继承父text元素上声明的textLength属性的错误。 如果您希望它们看起来相同,请将textLength属性放在textPath元素和文本上。 为什么? 因为事实证明,如果text元素上没有指定textLength属性,Firefox 开发人员也有同样的错误(这种情况已经有好几年了)。

Microsoft Edge 有一个完全不同的错误; 它无法处理Text和子TextPath元素之间的空格。 删除空格并将textLength属性放在texttextPath元素上后,它们看起来都相对相同(由于默认字体等方面的差异而略有不同)。 因此,三种不同浏览器上的三种不同错误——这就是人们经常喜欢使用库的原因!

下面的笔显示了如何解决这些问题:

请参阅 Bryan Rasmussen 的 Pen all circles Wrapped Text fixed TextLength。

请参阅 Bryan Rasmussen 的 Pen all circles Wrapped Text fixed TextLength。

我还删除了各种填充颜色,因为它可以更容易地查看文本换行。 删除填充颜色意味着我的小功能允许您循环浏览路径并查看它们的外观,除非我添加一个pointer-events="all"属性,否则我也添加了这些属性。

注意您可以在 Tiffany B. Brown 解释的“使用指针事件属性管理 SVG 交互”中阅读更多关于其原因的信息。

我们已经讨论了多弧路径的包装,现在让我们看看其他的。 由于我们有一条路径,因此文本将始终沿同一方向移动。

图片小路解释
M CX, CY
a R, R 0 1,0 -(R * 2), 0
a R, R 0 1,0 R * 2, 0
并使用translate函数在 X 轴上移动+R
我们的textPath的起始位置(因为我们没有以任何方式指定它)由我们的第一个结束弧-(R * 2)确定,给定弧本身的半径。
M (CX + R), CY
a R,R 0 1,0 -(R * 2),0
a R,R 0 1,0 (R * 2),0
与上一条路径相同。
M CX CY
m -R, 0
a R,R 0 1,0 (R * 2),0
a R,R 0 1,0 -(R * 2),0
由于我们在第一个弧中结束于(R * 2 ) ,显然我们将从相反的位置开始。 换句话说,这一条从我们前两条路径结束的地方开始。
M (CX - R), CY
a R,R 0 1, 1 (R * 2),0
a R,R 0 1, 1 -(R * 2),0
由于(R * 2) ,它从与最后一个相同的位置开始,但它是顺时针运行的,因为我们已将扫描标志属性(标记为黄色)设置为1

我们已经了解了如何将文本环绕在一个圆圈中的单个路径上。 现在让我们看看如何将这条路径分成两条路径,以及从中获得的好处。

将我们的路径分成几部分

您可以对路径中的文本做很多事情,即使用tspan元素实现风格效果、设置文本的偏移量或为文本设置动画。 基本上,无论您做什么,都会受到路径本身的限制。 但是通过将我们的多弧路径分解为单个弧路径,我们可以调整文本的方向、文本不同部分的 z 索引,并实现更复杂的动画。

首先,我们将要使用另一个 SVG 图像来显示一些效果。 我将使用前面提到的关于指针事件的文章中的菱形。 首先,让我们展示在其顶部放置单个路径圆形文本的情况。

假设我们的圈子是CX 295, CY 200, R 175 。 现在,按照循环路径方法,我们现在看到以下内容:

 M (CX - R), CY a R,R 0 1,1 (R * 2),0 a R,R 0 1,1 -(R * 2),0 

请参阅 Bryan Rasmussen 的 Pen SVG Amethyst。

请参阅 Bryan Rasmussen 的 Pen SVG Amethyst。

我不会谈论路径或文本大小、填充或描边颜色。 我们现在都应该明白这一点,并且能够让它成为我们想要的任何东西。 但是通过查看文本,我们可以立即看到一些缺点或限制:

  • 文本都向一个方向运行;
  • 将一些文字放在紫水晶后面可能会很好,尤其是在它写着 MAGAZINE 的地方。 为了使“M”和“E”在圆圈上对齐,“A”必须位于紫水晶的侧面较低点,这在另一方面感觉有点不平衡。 (我觉得'A'应该精确定位并指向那个点。)

如果我们想解决这些问题,我们需要将我们的单一路径一分为二。 在下面的笔中,我将路径分成了两条路径,(并将它们放入 SVG 的defs区域以供我们的textPath参考):

请参阅 Bryan Rasmussen 的 Pen SVG Amethyst 两条路径。

请参阅 Bryan Rasmussen 的 Pen SVG Amethyst 两条路径。

同样,假设我们的CX295, CY 200, R 175 ,那么两条路径的格式如下(对于顶部半圆形路径):

 M (CX - R), CY a R,R 0 1,1 (R * 2),0

下面是底部:

 M (CX + R), CY a R,R 0 1,1 -(R * 2),0

但是,我们仍然有圆形文本,它们都向同一方向移动。 要解决除 Edge 之外的所有问题,您所要做的就是将side="right"属性添加到包含 'MAGAZINE' textPathtext元素中。

使文本走向另一个方向

如果我们想支持尽可能多的浏览器,我们必须改变路径,而不是依赖不完全支持的side属性。 我们可以做的是复制我们的顶部半圆路径,但将扫描从1更改为0

前:

 M 120, 200 a 175,175 0 1, M 120, 200 a 175,175 0 1, 1 350,0 350,0

后:

 M 120, 200 a 175,175 0 1, M 120, 200 a 175,175 0 1, 0 350,0 350,0

但是我们的文本现在绘制在由扫描定义的内圈上,在不同的浏览器中看起来不太好。 这意味着我们将不得不移动路径的位置以与“Smashing”的“S”对齐,使路径的结尾 X 更大,并为文本设置一些偏移量。 如您所见,Firefox 与其他文本之间也存在一些差异,我们可以通过增加text元素的textLength属性以及从textPath中删除空格来改进(因为 Firefox 显然认为空格是有意义的)。

解决方案:

请参阅 Bryan Rasmussen 修复的 Pen SVG Amethyst 两条路径。

请参阅 Bryan Rasmussen 修复的 Pen SVG Amethyst 两条路径。

更改部分循环文本的 Z-Index

最后,我们想让我们的文本在紫水晶的前面和后面都出现。 嗯,这很容易。 还记得 SVG 的元素 z 索引是基于它们在标记中的位置吗? 所以如果我们有两个元素,元素1将被绘制在元素2后面。 接下来,我们要做的就是在 SVG 标记中向上移动一个text元素,以便在紫水晶之前绘制它。

您可以看到下面的结果,其中“MAGAZINE”这个词的部分被紫水晶的下端隐藏了。

请参阅 Bryan Rasmussen 的 Pen SVG Amethyst two paths z-index。

请参阅 Bryan Rasmussen 的 Pen SVG Amethyst two paths z-index。

如果您看一下标记,您会发现文本的下半圆已移动到绘制紫水晶的路径之前。

为我们圈子的各个部分设置动画

所以现在我们有能力通过将文本放入两个半圆来完全控制文本部分的方向性来制作圆形文本。 当然,这也可以用来制作文本的动画。 制作跨浏览器 SVG 动画确实是另一篇文章(或更多文章)的主题。 这些示例仅适用于 Chrome 和 Firefox,因为使用 SMIL 动画语法而不是 CSS 关键帧或 Greensock 等工具。 但它很好地指示了您可以通过为分解的圆圈设置动画来实现的效果。

拿下面的笔:

请参阅由 Bryan Rasmussen 制作的 Pen SVG Amethyst 两条路径动画。

请参阅由 Bryan Rasmussen 制作的 Pen SVG Amethyst 两条路径动画。

请按 codepen 上的“重新运行”按钮以查看正在运行的动画。 我们的循环文本的两个部分同时开始动画,但有不同的持续时间,因此它们在不同的时间结束。 因为我们正在为textLength属性设置动画,所以我们在每个文本下放置了两个animate指令——一个用于text元素(因此 Firefox 可以工作),一个用于textpath元素(因此 Chrome 可以工作)。

结论

在本文中,我们了解了如何将圆变成路径并再次返回,以便更好地了解何时需要优化路径,何时不需要。 我们已经看到如何将圆形变成路径让我们可以将文本放置在圆形路径上,以及如何进一步将圆形路径拆分为半圆形,并更全面地控制圆形文本组件的方向性和动画.

关于 SmashingMag 的进一步阅读

  • 重新思考响应式 SVG
  • 使用 SVGator 为 SVG 文件制作动画
  • 使用 CSS 为 SVG 设置样式和动画
  • 使用指针事件属性管理 SVG 交互