Svg之路径path - bobby169/Snap.svgDoc GitHub Wiki

path

[TOC]

<path> 元素通过指定一系列相互连接的线圆弧曲线绘制任意形状的轮廓。这些形状轮廓也像基本形状一样可以填充颜色或者绘制轮廓线。此外,路径(以及简写形式的基本形状)还可以用来定义裁剪区域或者透明遮罩的轮廓,所有描述轮廓的数据都放在 <path> 元素的 d 属性中(d 是 data 的缩写)。路径数据包括单个字符的命令,比如 M 表示 movetoL 表示 lineto,接着是该命令的坐标信息。

moveto、lineto和closepath

==每个路径都必须以 moveto 命令开始。==

命令字母为大写的 M,紧跟着一个使用逗号或空格分隔的 xy 坐标。这个命令用来设置绘制轮廓的“笔”的当前位置。

moveto 命令后面紧跟着一个或多个 lineto 命令,用大写 L 表示,它的后面也是由逗号或空格分隔的 x 和 y 坐标。示例中有三个路径。第一个绘制了一条线,第二个绘制了一个 直角,第三个绘制了两个 30 度的角。当我们使用另一个 moveto 命令“重新启用”画笔时, 会开始一条新的子路径。我们可以使用逗号或者空格分隔 x 和 y 坐标,正如示例中三个路径所示。

http://oreillymedia.github.io/svg-essentials-examples/ch07/moveto-lineto.html

<svg width="150px" height="150px" viewBox="0 0 150 150">
    <g style="stroke: black; fill: none;">
      <!-- single line -->
      <path d="M 10 10 L 100 10"/>
    
      <!-- a right angle -->
      <path d="M 10, 20  L 100, 20  L  100,50"/>
    
      <!-- two thirty-degree angles -->
      <path d="M 40 60 L 10, 60 L 40 42.68
        M 60, 60 L 90 60 L 60, 42.68"/>
    </g>
</svg>

如果想用 <path> 绘制矩形,可以采用绘制四条线的方式,也可以先绘制三条线,然后使用用大写 Z 表示的 closepath 命令绘制一条直线回到当前子路径的起点。

示例:使用 closepath

<g style="stroke: black; fill: none;">
    <!-- 四条线形式的矩形 -->
    <path d="M 10, 10 L 40, 10 L 40, 30 L 10, 30 L 10, 10"/>
    <!-- closepath绘制的矩形 -->
    <path d="M 60 10 L 90 10 L 90 30 L 60 30 Z"/>
    <!-- 两个30度角 -->
    <path d="M 40 60 L 10 60 L 40 42.68 Z
    M 60 60 L 90 60 L 60 42.68 Z"/>
</g>

相对moveto和lineto

前面的命令都使用大写字母表示,并且坐标都被假定为绝对坐标。如果使用小写命令字母,坐标会被解析为相对于当前的画笔位置。因此,下面两个路径是相同的。

<path d="M 10 10 L 20 10 L 20 30 M 40 40 L 55 35" style="stroke: black;"/>
<path d="M 10 10 l 10 0 l 0 20 m 20 10 l 15 -5" style="stroke: black;"/>

如果使用小写 m(moveto)启动路径,它的坐标会被解析为绝对位置,因为它没有参照位置来计算相对位置。本章中所有其他命令都有同样的大小写差别。大写命令的坐标是绝对的,小写命令的坐标是相对的。但是 closepath 命令没有坐标,它的大小写形式效果相同。

路径的快捷方式

水平和垂直lineto命令

水平线和垂直线很常用,足以成为快捷命令。路径可以使用 H 命令加绝对 x 坐标,或者 h 命令加相对 x 坐标,来指定一条水平线。类似地,垂直线可以使用 V 命令加绝对 y 坐标, 或者 v 命令加相对 y 坐标来指定。

简写形式 等价的冗长形式 效 果
H 20 L 20 current_y 绘制一条到绝对位置 (20, current_y) 的线
h 20 l 20 0 绘制一条到 (current_x + 20,current_y) 的线
V 20 L current_x 20 绘制一条到绝对位置 (current_x,20) 的线
v 20 l 0 20 绘制一条到 (current_x, current_y + 20) 的线

路径快捷方式表示法

  • 可以在 L 或者 l 后面放多组坐标,下面六个路径都绘制了菱形,前三个使用了绝对坐标,后三个使用了相对坐标。其中第三个和第六个中有一个值得关注的点——如果在 moveto 后面放置多对坐标,除了第一对坐标外,剩下的坐标都会被假设为它们跟在一个 lineto 后面。
<g style="fill:none; stroke: black">
    <path d="M 30 30 L 55 5 L 80 30 L 55 55 Z"/>
    <path d="M 30 30 L 55 5 80 30 55 55 Z"/>
    <path d="M 30 30 55 5 80 30 55 55 Z"/>
    <path d="m 30 30 l 25 -25 l 25 25 l -25 25 z"/> 
    <path d="m 30 30 l 25 -25 25 25 -25 25 z"/>
    <path d="m 30 30 25 -25 25 25 -25 25 z"/>
</g>
  • 所有不必要的空白都可以消除。命令字母后面不需要空白,因为所有的命令都是一个字母。数字和命令之间不需要空白,因为命令字母并不能作为数字的一部分。正数和负数之间也不需要空白,因为负数的前置减号并不能作为正数的一部分。这样,我们就可以进一步缩短前面列出的第三个和第六个路径:
<path d="M30 30 55 5 80 30 55 55Z"/>
<path d="m30 30 25-25 25 25-25 25z"/>

消除空白规则的另一个例子是,绘制一个宽度为 15 个单位、高度为 25 个单位,左上角在 坐标 (12, 24) 的矩形:

<path d="M 12 24 h 15 v 25 h -15 z"/><!-- 原始路径 --> 
<path d="M12 24h15v25h-15z"/> <!-- 更短的路径 -->

椭圆弧(非圆弧,圆弧更简单)

绘制直线段相对简单,因为路径上的两个点就唯一确定了它们之间的线段。但如果是曲线的话,由于在两个点之间可以绘制无限条曲线,因此我们必须给出额外信息,以在它们之间绘制一条曲线路径。这里要研究的最简单的曲线为椭圆弧,也就是绘制一个连接两个点的椭圆的一部分。

圆弧命令以字母 A(绝对坐标的缩写)或者 a(相对坐标的缩写)开始,后面紧跟以下 7 个参数。

  • 点所在椭圆的 x 半径和 y 半径。
  • 椭圆的 x 轴旋转角度 x-axis-rotation。
  • large-arc-flag,如果需要圆弧的角度小于 180 度,其为0; 如果需要圆弧的角度大于或等于 180 度,则为 1。
  • sweep-flag,如果需要弧以负角度绘制则为 0,以正角度绘制则为 1。
  • 终点的 x 坐标和 y 坐标(起点由最后一个绘制的点或者最后一个 moveto 命令确定)。

http://oreillymedia.github.io/svg-essentials-examples/ch07/arc.html

从其他弧线格式转换

你可能疑惑,为什么不能像其他矢量图形系统那样,通过给椭圆定义一个中心点、x 和 y 半径、起始角度和弧形角度,来指定一个弧形。这是规范中一个简单的方法,适合将弧形绘制为一个单一的对象。矛盾的是,这也正是 SVG 选择一种看似古怪的方法来指定弧形的原因。在 SVG 中,弧形并不能单独存在,它要成为线和曲线连接路径的一部分(比如, 一个圆角矩形就是由一系列线和椭圆弧组成的)。因此,通过终点指定弧形是合理的。

然而,有时候我们想要一个独立的半圆(或者更准确地说,半个椭圆)。假设有一个按照 如下方式指定的椭圆:

<ellipse cx="cx" cy="cy" rx="rx" ry="ry"/>

下面是绘制四种可能的半个椭圆的路径(括号中是要计算的代数表达式):

<!-- 北半球 -->
<path d="M (cx - rx) cy
A rx ry 0 1 1 (cx + rx) cy"/>

<!-- 南半球 -->
<path d="M (cx - rx) cy
A rx ry 0 1 0 (cx + rx) cy"/>

<!-- 东半球 -->
<path d="M cx (cy - ry)
A rx ry 0 1 1 cx (cy + ry)"/>

<!-- 西半球 -->
<path d="M cx (cy - ry)
A rx ry 0 1 0 cx (cy + ry)"/>

有时候我们可能想要绘制一个指定了中心和角度的任意弧,并且想要将它转换为 SVG 的 终点和范围格式。在另外一些情况下,还可能希望将弧形由 SVG 格式转换为中点和角度格式。第二种情况的数学运算相当复杂,详细信息可查看 SVG 规范。在附录 F 中可以看到这些转换方式的 JavaScript 版本。

贝塞尔曲线

弧形的特点是整洁,也能达到我们想要的效果,但是很少有人用“优雅”这个词来形容它。如果想要优雅,则需要使用通过二次和三次方程绘图生成曲线。数学家在几百年前就知道这些曲线了,但是绘制它们始终是一个计算很复杂的任务。法国汽车制造商雷诺公司的工程师皮埃尔· 贝塞尔和雪铁龙公司的物理学家和数学家 Paul de Casteljau 改变了这一状况,他们开发并推广了一种计算更简便的方式来生成这些曲线。

如果你用过 Adobe Illustrator 这类绘图程序,可以通过指定两个点以及移动 “控制柄”来绘制这些贝塞尔曲线。控制柄的端点称为控制点,因为它控制着曲线的形状。 当我们移动控制柄时,曲线以一种在外行看来非常神秘的方式变化着。Key Point 软件公司的图形设计师迈克 · 伍德(Mike Woodburn),提出了这种形象地表示控制点和曲线 关系的方式:想象一下线是由柔性金属制造的。控制点内部是一个磁铁,与控制点越近, 吸引力越强。

二次贝塞尔曲线Q

最简单的贝塞尔曲线是二次曲线。我们要指定起点、终点和控制点。假设有两个支柱放在线的两个端点,这两个支柱的交点就是控制点。拉紧两个支柱中点的是一根橡皮筋。其中曲线弯曲的地方正好和橡皮筋的中点相连。

起点 / 终点以及控制点之间的线与曲线的起点 / 终点成切线。曲线首先沿着到达控制点的线,然后弯曲到达中点,再顺着“支柱”线的方向。曲线最终从控制点沿着线向上滑动到 终点。Adobe Illustrator 这类程序只会展示一个“支柱”。下次使用这类程序时,可以在心里添加第二个支柱,这样产生的曲线就不那么神秘了。

这些都是理论,现在的实际问题是如何在 SVG 中生成曲线。可以通过在 <path> 数据中使 用 Q 或者 q 命令指定一个二次曲线。这个命令后面紧跟着两组指定控制点和终点的坐标。 大写命令意味着绝对坐标,小写命令意味着相对坐标。

下面的曲线起点在 (30, 75),终 点在 (300, 120),控制点在 (240, 30),指定在如下所示的 SVG 中。

<path d="M30 75 Q240 30, 300 120" style="stroke: black; fill: none;"/>

http://oreillymedia.github.io/svg-essentials-examples/ch07/quadratic-bezier.html

还可以在二次曲线命令后面指定多组坐标。这会生成一个多边贝塞尔曲线。假设想用 <path> 绘制一条从 (30, 100) 到 (100, 100) 并且控制点在 (80, 30) 的曲线,然后绘制一条到 (200, 80) 且控制点在 (130, 65) 的曲线。下面是这个路径的 SVG 示例,其中控制点坐标为(80,30) (130,65)了。

<path d="M30 100 Q 80 30, 100 100, 130 65, 200 80"/>

二次贝塞尔曲线平滑T

你可能心想:“哪里优雅了?该曲线是凹凸不平的。”这个评价是正确的。这是因为曲线相连接并不意味着它们在一起会很好看。这也是为什么 SVG 还提供了流畅的二次曲线命令, 用字母 T 表示(想要使用相对坐标,就用 t)。这个命令后面紧跟的是曲线的下一个端点; 如规范所说,控制点会自动计算,方法是“使新的控制点与上一条命令中的控制点相对于当前点中心对称”。

从数学角度来讲,新的控制点x2, y2基于上一条线段的端点x, y和上一个 控制点 x1, y1,按照如下规则计算:

x2 = x + (x - x1) = 2 * x - x1 y2 = y + (y - y1) = 2 * y - y1

下面是一条从 (30, 100) 到 (100, 100),控制点在 (80, 30),然后平滑过渡到 (200, 80) 的曲 线。

<path d="M30 100 Q 80 30, 100 100 T 200 80"/>

http://oreillymedia.github.io/svg-essentials-examples/ch07/smooth-quadratic-bezier.html

Q T 记:切图组合

三次贝塞尔曲线C

单个二次贝塞尔曲线有且只有一个顶点,或者每个曲线断都只有一个凹谷。虽然这些曲线比简单的弧线更有用,但是用三次贝塞尔曲线可以做得更好,它可以让同一个曲线图形内既有顶点也有凹谷。换句话说,三次曲线可以包含一个拐点(曲线从该点开始从一个方向 往另一个方向弯曲)。

二次曲线和三次曲线之间的区别是三次曲线有两个控制点,每个端点对应一个。生成三次曲线的方式与生成二次曲线的方式类似。从图 7-11 中可以看到,我们绘制了三条线连接端 点和控制点 (a),还连接了它们的中点,生成两条线 (b)。然后再连接 (b) 的中点生成一条 线 (c),它的中点确定了最终曲线上的一个点。注意曲线的起点、终点和中间角度都与控制 线成切线(相交)。

要指定一条三次曲线,使用 C 或者 c 命令。这个命令后面紧跟三组坐标,用来指定起点的 控制点、终点的控制点以及端点。和其他所有路径命令一样,大写命令意味着绝对坐标, 小写命令意味着相对坐标。上图中的曲线从 (20, 80) 到 (200, 120),控制点分别在 (50, 20) 和 (150, 60)。SVG 路径如下所示:

 <path d="M20 80 C 50 20, 150 60, 200 120"
         style="stroke: black; fill: none;"/>

http://oreillymedia.github.io/svg-essentials-examples/ch07/cubic-bezier.html

和二次曲线一样,也可以通过在三次曲线命令后面指定多组坐标,来构建多条连接在一起的三次曲线。第一条曲线的最后一个点会变成下一条曲线的第一个点,以此类推。这里有 一个 <path>,绘制了一条从 (30, 100) 到 (100, 100),控制点在 (50, 50) 和 (70, 20) 的三次曲 线;后面又紧跟着一条曲线,折回到 (65, 100),控制点在 (110,130) 和 (45, 150) 处。下面 是这个路径的 SVG,加粗的是控制点坐标:

<path d="M30 100 C 50 50, 70 20, 100 100,
110, 130, 45, 150, 65, 100"/>

三次贝塞尔曲线平滑S

如果想要保证曲线之间的连接平滑,可以使用 S 命令(或者如果想要使用相对坐标,就用 s)。在某种程度上,它和二次曲线的 T 命令类似,新的曲线会把上一条曲线的端点作为它 的起点,并且它的第一个控制点是上一个终点控制点的中心对称点。我们需要提供的只是 曲线的下一个端点的控制点,然后紧跟着的是下一个端点。

C S 记: 侧所组合

路径总结

命令 参数 效果
M m x y 移动到给定坐标
L l x y 绘制一条到给定坐标的线。可以提供多组坐标来绘制折线
H h x 绘制一条到给定 x 坐标的横线
V v y 绘制一条到给定 y 坐标的竖线
A a rx ry x-axis-rotation large-arc sweep x y 绘制一个从当前点到 (x,y) 的椭圆弧。椭圆上的 x 半径为 rx,y 半径为 ry。 椭圆旋转ry x-axis-rotation度。如果圆弧小于180度,则large-arc为 0;如果大于 180 度,则 large-arc 为 1。如果圆弧按顺时针方向绘制,则 sweep 为 1,否则为 0
Q q x1 y1 x y 绘制一条从当前点到 (x, y),控制点为 (x1, y1) 的二次贝塞尔曲线
T t x y 绘制一条从当前点到 (x, y) 的二次贝塞尔曲线。控制点是前一个 Q 命令的控制点的中心对称点。如果没有前一条曲线,当前点会被用作控制点
C c x1 y1 x2 y2 x y 绘制一条从当前点到 (x, y) 的三次贝塞尔曲线,(x1, y1) 为曲线的开始控制点,(x2, y2) 为曲线的终点控制点
S s x2 y2 x y 绘制一条从当前点到 (x, y) 的三次贝塞尔曲线,使用 (x2, y2) 作为新端点的 控制点。第一个控制点是前一个 C 命令的终点控制点的中心对称点。如果 前一个曲线不存在,当前点会被用作第一个控制点

<marker>元素

思考下面的路径,其中用了两条线和一个椭圆弧绘制如下所示的圆角:

<path d="M 10 20 100 20 A 20 30 0 0 1 120 50 L 120 110" style="fill: none; stroke: black;"/>

假设想要通过在开始位置放一个圆,在结束位置放一个实心三角形,以及在其他顶点放一些箭头来标记路径的方向。要实现这一效果,需要构建三个 <marker> 元素,然后让 <path> 元素引用它们。

 <defs>
     <marker id="mCircle" markerWidth="10" markerHeight="10" refX="5" refY="5">
       <circle cx="5" cy="5" r="4" style="fill: none; stroke: black;"/>
     </marker>
    
     <marker id="mArrow" markerWidth="6" markerHeight="10" refX="0" refY="4" orient="auto">
       <path d="M 0 0 4 4 0 8" style="fill: none; stroke: black;"/>
     </marker>
    
     <marker id="mTriangle" markerWidth="5" markerHeight="10" refX="5" refY="5" orient="auto">
       <path d="M 0 0 5 5 0 10 Z" style="fill: black;"/>
     </marker>
</defs>

<path d="M 10 20 100 20 A 20 30 0 0 1 120 50 L 120 110" style="marker-start: url(#mCircle);
marker-mid: url(#mArrow); marker-end: url(#mTriangle); fill: none; stroke: black;"/>

marker-mid 会附加给除路径 起点和终点以外的每个顶点。

另一个有用的属性是 markerUnits。如果设置为 strokeWidth,那么标记的坐标系统会被设定为单位等于笔画宽度。标记会与笔画宽度成正比,这是它的默认行为,通常也是我们想要的。如果设置这个属性为 userSpaceOnUse,标记的坐标系统会被假定为和引用该标记的 对象的坐标系统一样。不管笔画宽度为多少,标记都会保持相同的尺寸。

标记记录

如果想要路径的起点、中间和终点都使用相同的标记,无需指定所有的 marker-startmarker-midmarker-end 属性。只需使用 marker 属性引用想要的标记即可。

<defs>
    <marker id="mCircle" markerWidth="10" markerHeight="10" refX="5" refY="5">
        <circle cx="5" cy="5" r="4" style="fill: none; stroke: black;"/>
    </marker>
</defs>
<path d="M 10 20 100 20 A 20 30 0 0 1 120 50 L 120 110"
style="marker: url(#mCircle); fill: none; stroke: black;"/>

参考

⚠️ **GitHub.com Fallback** ⚠️