Paths
撰写时间:2025-04-20
修订时间:2026-01-28
path的geometry properties(几何属性)只有一个d属性,其值中又含有各个非常灵活的子命令,可绘制直线及丰富多彩的圆滑图形。
借用正则表达式,下面列出path的所有子命令:
在需要分别应对各个子命令的场合,可借助上面的代码来检测是否遗漏了特定的子命令。
d属性值的问题
在SVG 2.0规范中,path的d属性值可为none(初始值)或字符串。当其值为none时,各浏览器支持不一致,Chrome及Safari将抛出解析异常。因此,如果需要将该属性值设置为一个空值时,最稳妥的作法是将其值设置为空字符串。
绘制直线
M意为move
,用以移动当前光标位置。L命令意为line to
,指定一个点的位置后,将在当前光标位置与该点绘制一条直线。Z命令用以闭合图形。
因为可能需要多次调用L命令,因此多条连续的L命令可合并为1条L命令,其后跟着一系列的点的坐标。
上面,原来的L 100 400 L 600 400合并为L 100 400, 600 400。
多数命令,如上面的M命令与L命令,有大小写之分,大写的命令使用绝对坐标值,小写的命令使用相对坐标值。
使用小写命令l,用以指定偏移值,可清楚地看到,三角形的高为300px,底边为500px。
L命令后面的数据必须至少两个以上的分别代表(x, y)坐标轴的数值,不够简练。我们可以使用H或h命令来绘制水平直线,使用V或v命令来绘制垂直直线。这两个命令,因为已经明确了方向,因此只需跟有一个对应轴向上的偏移数值就行了。
可见,使用小写的h或v命令,可让我们十分便捷地完成绘图任务。它具有以下优点:
- 直观
- 便于添加众多顶点
- 方便修改
绘制圆弧
使用A或a命令来绘制一条圆弧。
综述
绘制圆弧的含义是,以当前点作为起点,绘制一条弧线到指定的终点。
当绘制一个圆或椭圆时,最直观的方法是根据圆心及半径来绘制。而A或a命令不是,相反,它会根据起点及终点,及其他相应信息,帮我们自动计算出圆心的位置,从而确定圆弧。但由于我们不能直观地推算出圆心的准确位置,因此,对程序员来说,绘制圆弧的命令不够直观。
下面这段代码,根据A命令的参数,反求出所对应的ellipse参数,然后以灰底虚线绘制一个未经旋转的椭圆作为背景,以在视觉上比较两种图像的异同。
红点是A命令的起点,绿点是A命令的终点,橙色是A命令所绘制的圆弧。
运行后,橙色圆弧正好压在椭圆的上半部。
逐个修改相应的参数,观察两个图形的变化,从而理解A命令各参数的含义。
例如,若只将zRotateDegree的值设置为25,则可看到圆弧与椭圆不再吻合,圆弧围绕Z轴进行了旋转,且椭圆的大小也发生了改变。这是因为圆弧起点与终点的位置未改变,为满足圆弧的其他参数要求,只能将椭圆的两个轴向上的半径按比例增大,导致相对应的椭圆的尺寸增大。
在SVG规范中,变量zRotateDegree对应于参数x-axis-rotation,其含义应是指X轴围绕Z轴旋转,但实际上,Y轴也同时围绕Z轴进行了旋转,因此更确切的说法是,XY平面围绕Z轴进行了旋转,故此这里使用zRotateDegree作为变量名。
变量rx及rx分别为圆弧的X轴半径及Y轴半径,但在一开始运行时,在Console
面板中可看到,所对应的椭圆的两个半径分别为100px及50px,比原值20px及10px均要大,这是因为SVG规范规定:如果圆弧的起点和终点距离太远,无法用所指定的rx及ry连接,SVG引擎应自动按比例缩放半径以满足几何约束。
修改变量largeArcFlag及sweepFlag的不同组合,观察圆弧的朝向。再结合修改终点的坐标值,观察圆弧的朝向。可以看出,参数largeArcFlag代表是否绘制长圆弧的含义很明确,但sweepFlag并非指我们原本认为的按顺时针还是按逆时针的方向的意思。该参数的具体含义,下面一节详细谈到。
通过上面的代码可以看出,SVG绘制圆弧的算法很复杂,只对数学家友好,但对程序员并不友好。
更多细节
由于需要以声明式的方式来绘制一个相对较为复杂的圆弧,SVG中绘制圆弧的算法比较独特。
上图中,我们需要从点Arc start
绘制一条圆弧至点Arc end
。
可以看出,这两个点为两个形状相同的椭圆的交点,或者说,这两个点来自两个椭圆。而若给定椭圆的长半径与短半径,从点Arc start
绘制一条圆弧至点Arc end
,共有4种绘制方法,根据这4种方法所绘制出来的圆弧在图中均已用红色标出。
4种结果依赖于需要绘制长圆弧还是短圆弧,以及是按逆时针还是按顺时针的方向来绘制。
假设我们需要绘制短圆弧。则图中第1行的第2列及第3列均为符合条件的短圆弧。第2列为按逆时针方向所绘制的短圆弧,它取自右上角这个椭圆的一小段圆弧。第3列为按顺时针方向所绘制的短圆弧,它取自左下角这个椭圆的一小段圆弧。
在这里,由于起点与终点已经确定下来,而从起点按逆时针方向绘制圆弧至终点,以及从起点按顺时针方向绘制圆弧至终点,将导致绘制出完全不同的两条圆弧。
现在,假设我们需要绘制长圆弧。则图中第2行的第2列及第3列均为符合条件的长圆弧。第2列为按逆时针方向所绘制的长圆弧,它取自左下角这个椭圆的一大段圆弧。第3列为按顺时针方向所绘制的长圆弧,它取自右上角这个椭圆的一大段圆弧。
换句话说,给定圆弧的起点与终点、长半径与短半径,这段圆弧可来自两个椭圆的相应部位。随着我们再接着确定长短圆弧及绘制方向的具体需求,这段圆弧也就随之确定下来。
我们注意到,尽管圆弧来自两个椭圆,但我们无需理会这两个椭圆如何绘制、它们的圆心各在哪里,以及所绘制的圆弧如何取自哪一个椭圆的哪一部位。所有这些,SVG的内部算法会自动帮我们确定。
可见,确定了圆弧的起点与终点,长半径与短半径,长弧或短弧,顺时针还是逆时针这几种关键因素后,这条圆弧也随之确定下来了。这就是SVG中使用A命令或a命令绘制圆弧的参数的由来,其原型如下:
- floatrx, ry
- numberx-axis-rotation
- booleanlarge-arc-flag
- booleansweep-flag
- floatx, y
A或a命令从当前点的位置,绘制一条X轴半径及Y轴半径分别为rx及ry的圆弧至(x, y)。
参数x-axis-rotation是椭圆的旋转值,以角度制 (degree) 为单位。
参数large-arc-flag指定是否需要绘制长圆弧。当值为0时,绘制短圆弧;当值为1时,绘制长圆弧。
参数sweep-flag指定是按负角度方向 (negative-angle
direction) 还是按正角度方向 (positive-angle
direction) 来绘制。当值为0时,按负角度方向绘制;当值为1时,按正角度方向绘制。
所谓正负方向,实际上是指椭代表椭圆公式的函数中的参数theta的变化方向:
当为负角度时,参数theta的值将从起点的角度减小至终点的角度;当为正角度时,参数theta的值将从起点的角度增大至终点的角度。
实际上,sweep-flag这个参数完全从数学角度来考量,但对于程序员来讲,很不直观,它不能直接地推测出圆弧的方向是顺时针还是逆时针。它需要结合参数large-arc-flag才能确定这一点。
绘制圆弧
下面是绘制圆弧的代码:
上面的代码,先从画布的中心(450, 300)绘制一条直线到(600, 300),则当前位置位于(600, 300)。A命令从该位置绘制了一条圆弧至(450, 150),圆弧的长短半径各为200及150。图中起点为红色,终点为绿色。
分别将代码中的large-arc-flag参数及sweep-flag参数的值改为1,尝试不同的组合,即可看到不同的效果。检查运行结果是否与上面图片中的效果一致。
绘制完整的圆或椭圆的问题
绘制圆弧时,注意圆弧的起点与终点不能相同。看下面代码。
我们准备从视口的中心点(400, 300),逆时针绘制一条长圆弧至它自己(400, 300),希望绘制一个完整的圆弧。但上面代码圆弧的终点先临时设置为(400, 299),则圆弧出现。
如果将终点坐标改为与起点坐标完全一致的(400, 300),圆弧不见了!
原因是,起点和终点相同的圆弧可以对应无限多个可能的圆弧(整圆),SVG渲染器无法确定具体要绘制哪一个。因此,SVG规范明确规定,当起点和终点相同时,圆弧命令应该被当作没有圆弧
来处理。
解决方法,使用circle或ellipse来绘制一个完全的圆或椭圆。如果必须使用path来绘制一个完整的圆或椭圆,可将起点的坐标改为一个差距很小的浮点值,如,将起点(400, 300)改为终点(400, 300.0001)。
另一种解决方法是分别绘制两个半径相等、但方向相反的半圆,从而合成一个完整的圆。下面先使用两个不同颜色的path来绘制两个半圆。
先绘制下面的半圆,再绘制上面的半圆。下面代码合成一个path。
使用相对格式的a命令时,参数中只有x-axis-rotation及x, y才使用偏移值,而其它参数,如rx, ry, large-arc-flag, sweep-flag等, 均不会受偏移值影响。
绘制曲线
发明了Bézier曲线绘制方法的科学家真是太给力了。对于任意的一条线段,仅额外加入2个、甚至仅加入1个控制点,直线立即转变为千变万化的、姿态曼妙的弧线。计算机绘图从未如此轻松、如此优雅。
Cubic Bézier曲线
C, c命令
C命令是一个三阶的贝塞尔曲线,使用两个端点及两个控制点来绘制曲线。
图像虽然绘制出来了,但很难看出4个端点与所生成图像的内在关系。下面通过拖动节点的方式来交互式地生成图像。
拖动4个节点,曲线随之而变化,下面的文本则输出其d属性值。
代码使用了MVC模式,dataWrapper为数据模型,修改该模型的值,可改变曲线的初始状态。
可以看出,由于SVG内置支持其各个元素的事件响应,因此在SVG中编写drag and drop
事件的代码非常轻松。
利用这个小工具,拖出我们想要的曲线后,复制其d属性值,再粘帖进其他的SVG代码,可方便地进行后续编辑。
这个功能可是PhotoShop, C4D等大腕软件的镇店之宝,而我们却仅用不到百行的代码就轻松地实现了此超酷的功能。
S, s命令
S命令属于一个连续绘制Cubic Bézier曲线的命令,在调用它之前,通常已有一个绘制Cubic Bézier曲线的命令。
上面代码,有两个绘制Cubic Bézier曲线的命令,第一个是C命令,第二个是S命令。
S命令的参数较简单,只有一个终止端点的控制节点(303, 216),以及一个终止端点(312, 120)。
实际上,S命令照样使用两个端点及两个控制点,但起始端点取自于当前点(204, 119)(也即S命令之前的C命令中的终止端点),起始端点的控制节点取自于上条曲线的第二个控制节点(260, 47)相对于当前点(204, 119)的镜像。
理解起来很费劲,但在交互式的图形应用中,这些概念立即变得清晰起来。
橙色的点,既是第一条C命令的终止端点,也成为第二条S命令的起始端点。
而黑色虚框的圆点,是S命令的起始端点的控制节点,它是C命令的终止控制节点(蓝色)相对于C命令终止端点(橙色)的镜像。由于它是SVG引擎自动求出的,因此在交互界面中不能拖动。相反,拖动蓝色控制节点,则可看到黑色控制节点的即时反应。
没有平滑曲线时的区别
可以看出,当第2个节点不使用S命令,而使用C命令时,则可以独立地控制2个控制点;移动其中一个控制点,另一条曲线不受任何影响。
两个控制点与黄色端点分别组成了两条直线。当这两条直线的夹角为180°时,曲线的弧度过渡得最为平滑。因此使用S命令所绘制出来的曲线,也称为平滑曲线(smooth curve)。
当S命令前面无C或S命令时
当S命令的前面没有C, c, S, 或s命令时, 则将S命令前面的当前点用作起始端点,S命令中唯一的控制点用以同时控制起始端点及终止端点。此时的效果,等同于二阶的Q命令(详见下节)。
注,SVG规范将此问题表述为:在此情况下照样存在两个控制点,但第一个控制点移到当前点的位置,即两个点塌陷为一个点,故看不到第一个控制点到当前点的连线。
两种表述,视觉效果均完全一样。
使用c命令绘制半圆
除了使用a命令绘制一个半圆之外,也可使用c命令来逼近一个半圆圆弧。
下面代码,使用c命令来绘制一个从(50, 100)到(150, 100)的上半圆弧。
从视觉上看,红色圆弧精准地压住了灰色圆圈的上半圆弧。
c命令的两个控制点,其X轴坐标值分别对应于圆弧的起始点与终点。难点在于如何根据半径r来求出它们的Y轴坐标值。
对于任意半径r,使用单个三次贝塞尔曲线来逼近上半圆的公式是:
上面r的值为50,则控制点Y轴的偏移值为:
Quadratic Bézier曲线
Q命令
Q命令是一个二阶的贝塞尔曲线,使用两个端点及一个控制点来绘制曲线。
交互。
T命令
T命令的控制点为前面的Q或T命令的控制点相对于当前点的镜像。
交互。
如果T命令前面没有Q或T命令,则控制点坍塌至当前点,从而绘制出一条直线。
d属性子命令总结
下面分类列出d属性的所有子命令。
| 分类 | 符号 | 名称 | 参数 | 简介 |
|---|---|---|---|---|
| 定位 | M, m | moveto | (x y)+ |
|
| 关闭图形 | Z, z | closepath | N/A | 从当前点绘制一条直线至初始点,以关闭当前子路径。 |
| 绘制直线 | L, l | lineto | (x y)+ |
|
| H, h | horizontal lineto | x+ |
|
|
| V, v | vertical lineto | y+ |
|
|
| 绘制三次贝塞尔曲线 | C, c | curveto | ( x1 y1 x2 y2 x y )+ |
|
| S, s | smooth curveto | ( x2 y2 x y )+ |
|
|
| 绘制二次贝塞尔曲线 | Q, q | quadratic Bézier curveto | ( x1 y1 x y )+ |
|
| T, t | smooth quadratic Bézier curveto | (x y)+ |
|
|
| 绘制圆弧 | A, a | elliptical arc | ( rx ry x-axis-rotation large-arc-flag sweep-flag x y )+ |
|
Path DOM
本节部分的APIs只适用于Safari,Chrome目前尚未支持。
Canvas的Path2D不能直接操控子路径。而SVG的SVGPathElement则提供了直接操控子路径的方法。
path的类为SVGPathElement,有以下添加各种子路径的方法:
| 类别 | 方法名称 | d属性中的命令 | 作用 |
|---|---|---|---|
| 指定当前位置 | createSVGPathSegMovetoAbs | M | 使用绝对值,将指定位置设置为当前位置 |
| createSVGPathSegMovetoRel | m | 使用相对值,将指定位置设置为当前位置 | |
| 绘制直线 | createSVGPathSegLinetoAbs | L | 使用绝对值,从当前位置绘制一条直线至指定位置 |
| createSVGPathSegLinetoRel | l | 使用相对值,从当前位置绘制一条直线至指定位置 | |
| createSVGPathSegLinetoHorizontalAbs | H | 使用绝对值,从当前位置绘制一条水平直线至指定位置 | |
| createSVGPathSegLinetoHorizontalRel | h | 使用相对值,从当前位置绘制一条水平直线至指定位置 | |
| createSVGPathSegLinetoVerticalAbs | V | 使用绝对值,从当前位置绘制一条竖直的直线至指定位置 | |
| createSVGPathSegLinetoVerticalRel | v | 使用相对值,从当前位置绘制一条竖直的直线至指定位置 | |
| 绘制圆弧 | createSVGPathSegArcAbs | A | 使用绝对值,从当前位置绘制一条圆弧至指定位置 |
| createSVGPathSegArcRel | a | 使用相对值,从当前位置绘制一条圆弧至指定位置 | |
| 绘制Cubic Bézier曲线 | createSVGPathSegCurvetoCubicAbs | C | 使用绝对值,从当前位置绘制一条Cubic Bézier曲线至指定位置 |
| createSVGPathSegCurvetoCubicRel | c | 使用相对值,从当前位置绘制一条Cubic Bézier曲线至指定位置 | |
| createSVGPathSegCurvetoCubicSmoothAbs | S | 使用绝对值,从当前位置绘制一条平滑的Cubic Bézier曲线至指定位置 | |
| createSVGPathSegCurvetoCubicSmoothRel | s | 使用相对值,从当前位置绘制一条平滑的Cubic Bézier曲线至指定位置 | |
| 绘制Quad Bézier曲线 | createSVGPathSegCurvetoQuadraticAbs | Q | 使用绝对值,从当前位置绘制一条Quad Bézier曲线至指定位置 |
| createSVGPathSegCurvetoQuadraticRel | q | 使用相对值,从当前位置绘制一条Quad Bézier曲线至指定位置 | |
| createSVGPathSegCurvetoQuadraticSmoothAbs | T | 使用绝对值,从当前位置绘制一条平滑的Quad Bézier曲线至指定位置 | |
| createSVGPathSegCurvetoQuadraticSmoothRel | t | 使用相对值,从当前位置绘制一条平滑的Quad Bézier曲线至指定位置 | |
| 关闭路径 | createSVGPathSegClosePath | z, Z | 关闭路径 |
下面使用上述方法来构建一个正方形,然后,通过SVGPathElement的pathSegList来检视每个子路径。
了解这些APIs的细节后,我们就可以通过代码来创建可动态交互的路径了。
