第 7 章 路径
所有基本形状都是 <path> 元素的简写形式
<path> 元素更通用,它通过指定一系列相互连接的线、圆弧、曲线绘制任意形状的轮廓
所有描述轮廓的数据都放在 <path> 元素的 d 属性中(data 的缩写),路径数据包括单个字符的命令,如 M 表示 moveto,L 表示 lineto,接着是该命令的坐标信息
moveto、lineto、closepath
每个路径都必须以 moveto 命令开始
- 命令字母为
M,紧跟着一个使用逗号或空格分隔的x和y坐标。这个命令用来设置绘制轮廓的“笔”的当前位置 moveto命令后面紧跟着一个或多个lineto命令,L表示,它的后面也是由逗号或空格分割的x和y坐标- 每次使用新的
moveto命令时,都会重新开始一条新的子路径
<!-- @format -->
<svg width="150px" height="150px" xmlns="http://www.w3.org/2000/svg">
<g style="stroke: black; fill: none">
<path d="M 10,10 L 100,10"></path>
<path d="M 10,20 L 100,20 L 100,50"></path>
<path d="M 40,60 L 10,60 L 40,42.68 M 60,60 L 90,60 L 60,42.68"></path>
</g>
</svg>
如果要绘制封闭的图形,可以使用 closepath 命令,Z 表示。使用 closepath 命令绘制出来的封闭图形,开始线和结束线会被连接到一起,形成一个连续的形状,如果线条宽度更粗或者设置了 stroke-linecap 和 stroke-linejoin 效果时,区别更明显
<!-- @format -->
<svg width="150px" height="150px" xmlns="http://www.w3.org/2000/svg">
<g style="stroke: black; fill: none">
<!-- 四条线形式的矩形 -->
<path d="M 10,10 L 40,10 L 40,30 L 10,30 L 10,10"></path>
<!-- closepath 绘制的矩形 -->
<path d="M 60,10 L 90,10, L 90,30 L 60,30 Z"></path>
<!-- 两个 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"
></path>
</g>
</svg>
相对 moveto 和 lineto
- 小写字母
l会被解析为相对于当前的画笔位置 - 小写字母
m会被解析为相对位置,相对于上一个坐标的,如果是第一个,则是相对于坐标原点
本章中所有其他命令都有同样的大小写差别,大写为绝对位置,小写为相对位置。
closepath命令除外,因为它没有坐标,因此大小写效果相同
路径的快捷方式
水平和垂直 lineto 命令
| 简写形式 | 等价的冗长形式 | 效果 |
|---|---|---|
H 20 | L 20 y | 绘制一条到绝对位置 (20, y) 的线(即 y 不变,画横线) |
h 20 | l 20 0 | 绘制一条,相对上一个点到 (x + 20, y) 的线 |
V 20 | L x 20 | 绘制一条到绝对位置 (x, 20) 的线 (即 x 不变,画竖线) |
v 20 | l 0 20 | 绘制一条,相对上一个点到 (x, y + 20) 的线 |
路径快捷方式表示法
- 可以在
L或者l后面放多组坐标,如<polyline>元素中那样 - 如在
M或者m后面放多组坐标,则除了第一对坐标外,其他坐标会被解析为跟在一个lineto后面 - 所有不必要的空白都可以消除(为了代码可读性,请自行斟酌是否需要消除)
- 命令字母后面的空白
- 数字和命令之间的空白
- 正数和负数之间的空白
还可在水平和垂直
lineto命令后面放置多个坐标轴,但只在使用线标记时才会看到效果,目前还没讨论过线标记。H 25 35 45和H 45相同,V 11 13 15和V 39相同
椭圆弧
圆弧命令以 A 或者 a 开始,后面紧跟以下 7 个参数
- 点所在椭圆的
x半径和y半径 x-axis-rotation:椭圆的x轴旋转角度(默认为 0,与坐标系统的x轴平行)large-arc-flag:如果需要圆弧的角度小于 180 度,则为 0,否则为 1sweep-flag:如果需要圆弧以负角度绘制则为 0,否则为 1- 终点的
x坐标和y坐标(起点由前一个绘制的点或者最后一个moveto命令确定)

<!-- @format -->
<svg width="400px" height="200px" xmlns="http://www.w3.org/2000/svg">
<g style="fill: none">
<!-- b -->
<path d="M 125,75 A100,50 0 0,0 225,125" style="stroke: red" />
<!-- c -->
<path d="M 125,75 A100,50 0 0,1 225,125" style="stroke: blue" />
<!-- d -->
<path d="M 125,75 A100,50 0 1,0 225,125" style="stroke: black" />
<!-- e -->
<path d="M 125,75 A100,50 0 1,1 225,125" style="stroke: orange" />
</g>
</svg>
一个太极图形
<!-- @format -->
<svg width="400px" height="300px" xmlns="http://www.w3.org/2000/svg">
<!-- 灰色投影 -->
<ellipse cx="154" cy="154" rx="150" ry="120" style="fill: #999999" />
<!-- 浅蓝色椭圆 -->
<ellipse cx="152" cy="152" rx="150" ry="120" style="fill: #cceeff" />
<!-- 浅红色大半圆填充符号的上半部分,其下方“浸入"符号左下方的浅红色小半圆 -->
<path
d="M 302 152 A 150 120, 0, 1, 0, 2 152
A 75 60, 0, 1, 0, 152 152"
style="fill: #ffcccc"
/>
<!-- 浅蓝色小半圆,填充符号右上方 -->
<path d="M 152 152 A 75 60, 0, 1, 1, 302 152" style="fill: #cceeff" />
</svg>
- 不能使用一个
<path>命令绘制一个完整的椭圆。如果弧形的起点和终点相同,则有无数种方式定位椭圆。SVG阅读器会跳过这样的圆弧命令。如果指定的椭圆半径太小,导致不能覆盖起点和终点,则SVG阅读器会扩大椭圆知道它足够覆盖起点和终点- 对于如何处理超出范围的参数,请参考 弧形实现说明规范
从其他弧线格式转换
SVG 之所以选择这种看似古怪的方法来实现弧形,是因为:在 SVG 中,弧形并不能单独存在,它要成为线和曲线连接路径的一部分(如,一个圆角矩形就是由一系列的线和椭圆弧组成的)
假设有一个按照如下方式指定的椭圆
<ellipse cx="cx" cy="cy" rx="rx" ry="ry" />
下面是绘制 4 种可能的半个椭圆的路径(括号中是要计算的代数表达式)
<!-- 北半球 -->
<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版本
贝塞尔曲线
二次贝塞尔曲线
以 Q 或 q 命令指定一个二次曲线,后面紧跟 2 组坐标:控制点、终点(起点由前一个绘制的点或者最后一个 moveto 命令确定)
<!-- @format -->
<svg>
<path d="M30 75 Q240 30, 300 120" style="stroke: black; fill: none" />
</svg>
还可以在二次曲线命令后面指定多组坐标,这样会生成一个多边贝塞尔曲线
<!-- @format -->
<svg>
<path
d="M30 100 Q 80 30 100 100 130 65 200 80"
style="stroke: black; fill: none"
/>
</svg>
使用 T 或 t 可以实现平滑的二次贝塞尔曲线,其会自动计算新的控制点,方法为:使新的控制点与上一条命令中的控制点相对于当前点中心对称
<!-- @format -->
<svg>
<path
d="M30 100 Q 80 30 100 100 T 200 80"
style="stroke: black; fill: none"
/>
</svg>
从数学角度来讲,新的控制点的坐标
(x2, y2)基于上一条线段的端点坐标(x, y)和上一个控制点的坐标(x1, y1),按照如下规则计算
x2 = x + (x - x1) = 2 * x - x1y2 = y + (y - y1) = 2 * y - y1
三次贝塞尔曲线
使用 C 或者 c 命令,后面紧跟 3 组坐标:起点的控制点、终点的控制点、终点
<!-- @format -->
<svg>
<path
d="M20 80 C 50 20, 150 60, 200 120"
style="stroke: black; fill: none"
/>
</svg>
三次贝塞尔曲线也可以通过在三次曲线命令后面指定多组坐标,来构建多条连接在一起的三次曲线,第一条取消的最后一个点会变成下一条曲线的第一个点,以此类推。
使用 S 或 s 命令可以实现平滑的三次贝塞尔曲线,其规则与二次贝塞尔曲线的 T 命令类似,会自动计算第一个控制点,因此只需提供另外两组坐标即可
<!-- @format -->
<svg>
<path
d="M30 100 C 50 30 70 50 100 100 S 150 40 200 80"
style="stroke: black; fill: none"
/>
</svg>
路径总结
大写命令使用绝对坐标,小写命令使用相对坐标
| 命令 | 参数 | 效果 |
|---|---|---|
M m | x y | 移动到给定坐标 |
L l | x y | 绘制一条到给定坐标的线,可以提供多组坐标来绘制折线 |
H h | x | 绘制一条到给定 x 坐标的横线 |
V v | y | 绘制一条到给定 y 坐标的横线 |
A a | rx ry x-axis-ratation large-arc sweep x y | 绘制一个从当前点到 (x, y) 的椭圆弧。椭圆上的 x 半径为 rx,y 半径为 ry。椭圆旋转 x-axis-ratation 度。如果圆弧小于 180 度,则 large-arc 为 0,反之为 1,如果圆弧按顺时针方向绘制,则 sweep 为 1,反之为 0 |
Q q | x y x1 y1 | 绘制一条从当前点到 (x, y),控制点为 (x1, y1) 的二次贝塞尔曲线 |
T t | x y | 绘制一条从当前点到 (x, y) 的二次贝塞尔曲线。控制点是前一个 Q 命令的控制点的中心对称点。如果没有前一条曲线,当前点会被用作控制点 |
C c | x y x1 y1 x2 y2 | 绘制一条从当前点到 (x, y) 的三次贝塞尔曲线,(x1, y1) 为曲线的开始控制点,(x2, y2) 为曲线的终点控制点 |
S s | x y x2 y2 | 绘制一条从当前点到 (x, y) 的三次贝塞尔曲线,(x2, y2) 为曲线的终点控制点。第一个控制点是前一个 C 命令的终点控制点的中心对称点。如果前一个曲线不存在,当前点会被用作第一个控制点 |
路径和填充
第 4 章介绍的填充规则同样适用于路径,路径中不仅可以有相交线,还可以有“缺口”
<!-- @format -->
<svg >
<defs>
<g id="g" style="stroke: red">
<!-- 顺时针方向的路径 -->
<path
d="M 0 0, 60 0, 60 60, 0 60 Z M 15 15, 45 15, 45 45, 15 45Z"
/>
</g>
<g id="g2" style="stroke: red">
<!-- 外部路径为顺时针方向,内部路径为逆时针方向 -->
<path
d="M 0 0, 60 0, 60 60, 0 60 Z M 15 15, 15 45, 45 45, 45 15Z"
/>
</g>
</defs>
<use xlink:href="#g" style="fill-rule: nonzero" />
<use xlink:href="#g2" style="fill-rule: nonzero" x="100" />
<use xlink:href="#g" style="fill-rule: evenodd" y="80" />
<use xlink:href="#g2" style="fill-rule: evenodd" x="100" y="80" />
</svg>
<marker> 元素
用于给图形添加起终点标记和绘制方向标记
<!-- @format -->
<svg>
<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="4"
and
markerHeight="8"
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;
"
/>
</svg>
<marker>自身不会显示,但是可以把它放到<defs>元素中,因为它是存放可复用元素的- 一个
<marker>是一个独立的图形,它拥有自己私有的坐标,因此必须指定它的markerWidth和markerHeight - 在
<marker>开始和结束标签之间绘制标记图形 - 在
<path>中,通过marker-start属性引用<marker>(标记被认为是表现而不是结构的一部分,这是灰色地带,可以支持任何一种观点) - 默认情况下,开始标记的
(0, 0)与路径的开始坐标对齐,<marker>中的refX和refY属性指定哪个坐标(标记的坐标系统中)与路径的开始坐标对齐 - 必须明确设置标记的
orient属性为auto,这会让标记自动旋转以匹配路径的方向(确切地讲,旋转角度是线条进入标记图形和线条退出标记图形时的角度平均值。也可以指定度数,此时标记始终按照指定的度数旋转) - 如果将
markerUnits属性strokeWidth(默认值):标记的坐标系统会被设置为单位等于笔画宽度,标记会与笔画宽度成正比,通常也是我们想要的效果userSpaceOnUse:标记的坐标系统会被假定为引用该标记的对象的坐标系统一样,不管笔画宽度为多少,标记都会保持相同的尺寸
标记记录
- 如果想要路径的起点、中间、终点都使用相同的标记,只需使用
marker属性引用想要的标记即可 - 可以在
<marker>元素上设置viewBox和preserveAspectRatio属性,以更好地控制它的显示效果。例如可以使用viewBox定义网格,让坐标原点位于标记的中心,而不是使用refX和refY - 可以在
<polygon>、<polyline>、<line>、<path>中引用<marker> - 可以在标记中的路径添加标记,但是不建议。此时第二个标记必须适配由第一个标记的
markerWidthmarkerHeight建立的矩形。如果要实现这种效果,最好将从属标记也作为主标记的一部分,而不是嵌套 - 确保没有为标记定义自己作为从属标记。使用如下
CSS规则可能会发生这种情况
path {
marker: url(#start);
}
/* 为了防止出现自己定义自己,添加如下样式 */
marker#start path {
marker: none;
}