o'Reill的SVG精髓(第二版)学习笔记——第七章
第七章:路径
所有描述轮廓的数据都放在<path>元素的d属性中(d是data的缩写)。路径数据包括单个字符的命令,比如M表示moveto,L表示lineto。接着是该命令的坐标信息。
7.1moveto、lineto、closepath
每个路径都必须以moveto命令开始。
命令字母为大写的M,紧跟着一个使用逗号或空格分隔的x和y坐标。这个命令用来设置绘制轮廓的“笔”的当前位置。
moveto命令后面紧跟着一个或多个lineto命令,用大写字母L表示,它的后面也是由逗号或者空格分隔的x和y坐标。
下例中,第一个绘制了一条线,第二个绘制了一个直角,第三个绘制了两个30度的角。
当我们使用另一个moveto命令“重新启用”画笔时,会开始一条新的子路径。我们可以使用逗号或者空格分隔x和y坐标。
在线示例:http://oreillymedia.github.io/svg-essentials-examples/ch07/moveto-lineto.html
<!-- 使用moveto和lineto --> <svg width="150px" height="150px" viewBox="0 0 150 150" xmlns="http://www.w3.org/2000/svg"> <g style="stroke:black;fill:none;"> <!-- 一条线 --> <path d="M 10 10 L 100 10" /> <!-- 一个直角 --> <path d="M 10,20 L 100,20 L 100,50" /> <!-- 两个30°角 --> <path d="M 40 60 L 10,60 L 40 42.68 M 60,60 L 90 60 L 60,42.68" /> </g> </svg>
效果图:
仔细观察最后一个路径,它使用了逗号而不是空格来分隔坐标:
值 | 作用 |
---|---|
M 40 60 | 移动画笔到(40,60) |
L 10 60 | 绘制一条线到(10,60) |
L 40 42.68 | 绘制一条线到(40,42.68) |
M 60 60 | 启动一个新的自路径,移动画笔到(60,60)——不会绘制线条 |
L 90 90 | 绘制一条线到(90,60) |
L 60 42.68 | 绘制一条线到(60,42.68) |
如果想要用<path>绘制矩形,可以采用绘制四条线的方式,也可以先绘制三条线,然后使用大写的Z表示closepath命令绘制一条直线回到当前子路径的起点。
<!-- 使用closepath --> <svg width="200px" height="200px" viewBox="0 0 200 200"> <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> </svg>
效果图:
最后一个路径:
值 | 作用 |
---|---|
M 40 60 | 移动画笔到(40,60) |
L 10 60 | 绘制一条线到(10,60) |
L 40 42.68 | 绘制一条线到(40,42.68) |
Z | 通过绘制一条直线到(40,60)来关闭路径并启动子路径 |
M 60 60 | 启动一个新的自路径,移动画笔到(60,60)——不会绘制线条 |
L 90 90 | 绘制一条线到(90,60) |
L 60 42.68 | 绘制一条线到(60,42.68) |
Z | 通过绘制一条直线到(60,60)来关闭路径并启动子路径 |
使用四条线绘制矩形和使用closepath命令绘制矩形还有另外一个区别。当关闭路径时,开始线和结束线会被链接到一起,形成一个有样式的连续形状。如果使用粗画笔或者设置stroke-linecap以及stroke-linejoin效果,区别就明显了。
<svg width="200px" height="200px" viewBox="0 0 200 200"> <g style="stroke:gray;fill:none;stroke-width:8;"> <!-- 四条线形式的矩形 --> <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" /> </g> </svg>
我试了一下并放大并没有区别:
7.2相对moveto和lineto0
如果使用小写命令字母,坐标会被解析为相对于当前的画笔位置。因此,下面两个路径是相同的。
<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;" />
大写命令的坐标时绝对的,小写命令的坐标是相对的。但是closepath命令没有坐标,它的大小写形式效果相同。
7.3路径的快捷方式
7.3.1水平和垂直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)的线 |
因此,下面的路径绘制了一个宽度为15单位,高度为25单位的矩形,并且其左上角在坐标(12,24)处。
<path d="M 12 24 h 15 v 25 h -15 z" />
7.3.2 路径快捷方式表示法
可以在L或者l和面放多组坐标,正如在<polyline>元素中的那样。下面留个路径都绘制了菱形,前三个使用了绝对坐标,后三个使用了相对坐标。其中第三个和第六个中有一个值得关注的点——如果在moveto后面放置多对坐标,除了第一对坐标外,剩下的坐标都会被假设为它们跟在一个lineto后面。
<svg width="200px" height="200px" viewBox="0 0 200 200"> <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> </svg>
所有不必要的空白都可以消除。命令字母后面不需要空白。数字和命令之间不需要空白。正数和负数之间不需要空白。这样我们就可以进一步缩短前面列出的第三个和第六个路径:
<path d="M30 30 55 5 80 30 55 55Z" /> <path d="m30 30 25-25 25 25-25 25 z" />
7.4椭圆弧
在线示例:http://oreillymedia.github.io/svg-essentials-examples/ch07/arc.html
圆弧命令以字母A(绝对坐标的缩写)或者a(相对坐标的缩写)开始,后面紧跟以下七个参数
①点所在椭圆的x半径和y半径。
②椭圆的x轴旋转角度x-axis-rotation。
③large-arc-flag,如果需要圆弧的角度小于180度,其为0;如果需要圆弧的角度大于或者等于180度,则为1。
④swee-flag,如果小阳台弧以负角度绘制则为0,以正角度绘制则为1.
⑤终点的x坐标和y坐标(起点由最后一个绘制的点或者最后一个moveto命令确定。)
<!-- 使用椭圆弧 --> <svg width="400px" height="300px" viewBox="0 0 400 300"> <!-- 灰色投影 --> <ellipse cx="154" cy="154" rx="150" ry="120" style="fill:#999;" /> <!-- 浅蓝色椭圆 --> <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>
效果图:
我们不能使用一个路径命令绘制一个完整的椭圆;如果弧形的起点和终点相同,则有无数种方式定位椭圆。SVG阅读器会跳过这样的圆弧命令。如果指定的椭圆半径太小,导致不能覆盖起点和终点,则SVG阅读器会扩大椭圆直到它足够股改起点和终点。
如何处理超过范围的参数,请参考:http://www.w3.org/TR/SVG11/implnote.html#ArclmplementationNotes
7.5从其他弧线格式转换
在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)" />
有时候我们可能想要绘制一个指定了中心和角度的任意弧,并且想要将它转换为SCG的终点和范围格式。在另一些情况下,还可能希望将弧形由SVG格式转换为中点和角度格式。第二种情况的数学运算相当犊砸。
7.6贝塞尔曲线。
7.6.1 二次贝塞尔曲线
可以通过在<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实例。
<svg width="400px" height="400px"> <path d="M30 100 Q 80 30,100 100,130 65,200 80" style="stroke: black;fill:none;" /> </svg>
如图所示:
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" style="stroke: black;fill:none;" />
二次贝塞尔曲线:http://oreillymedia.github.io/svg-essentials-examples/ch07/smooth-quadratic-bezier.html
7.6.2 三次贝塞尔曲线
单个二次贝塞尔曲线有且只有一个顶点,或者每个曲线段都有一个凹谷,虽然这些曲线比简单的弧线更有用,但是用三次贝塞尔曲线可以更好。它可以让同一个曲线图形内既有顶点也有凹谷,换句话说,三次曲线可以包含一个拐点(曲线从该点开始从一个方向往另一个方向弯曲。)
三次曲线和二次曲线之间的区别就是三次曲线有两个控制点,每个端点对应一个。生成三次曲线的方式与生成二次曲线的方式类似。
要指定一条三次曲线,使用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" style="stroke: black;fill:none;" />
如图:
如果想要保证曲线之间的连接平滑,可以使用s命令(或者如果想要使用相对坐标,就用s)。在某种程度上,它和二次曲线的T命令类似。新的曲线会把上一条曲线的端点作为它的起点,并且它的第一个控制点是上一个终点控制点的中心对称点。我们需要提供的只是曲线的下一个端点的控制点,然后紧跟着的是下一个端点。
从(30,100)到(100,100),控制点为(50,30)和(70,50)。然后它平滑过渡到(200,80),使用(150,40)作为终点控制点。
<path d="M30 100 C 50,50, 70 50,100 100 S 150 40,200 80" style="stroke: black;fill:none;" />
如图:
7.7路径总结。
大写命令使用绝对坐标,小写命令使用相对坐标
命令 | 参数 | 效果 |
---|---|---|
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命令的终点控制点的中心对称点。如果前一个曲线不存在,当前点会被用作第一个控制点。 |
7.8路径和填充
<!-- 在路径中使用不同的fill-rule值 --> <svg width="400px" height="400px"> <!-- 顺时针方向的路径 --> <path d="M 0 0,60 0,60 60,0 60 Z M 15 15,45 15,45 45,15 45Z" style="stroke: black;fill:none;" /> <!-- 外部路径为顺时针方向,内部路径为逆时针方向 --> <path d="M 0 0,60 0,60 60,0 60 Z M 15 15,15 45,45 45,45 15Z" style="stroke: black;fill:none;" /> </svg>
7.9<marker>元素
其中用了两条线和一个椭圆弧绘制:
<path d="M 10 20 100 20 A 20 30 0 0 1 120 50 L120 110" style="fill:none;stroke:black;" />
假设想要通过在开始位置放一个圆形,在结束位置放一个实心三角形,以及在其他顶点放置一些箭头来标记路径的方向。需要构建三个<marker>元素,然后让<path>元素引用它们。
标记就是一个独立的图形,它拥有自己私有的坐标,因此必须在开始标记<marker>中指定它的markerWidth和markerHeight。<marker>元素自身不会显示,但是可以把它放到<defs>元素中,因为它是存放可服用元素的。
因为我们想要圆形位于路径的开始位置,因此给<path>的style属性添加了一个marker-start。这个属性的值是刚才创建的<marker>元素的URL。
<!-- 圆形标记初试 --> <svg width="400px" height="400px" viewBox="0 0 400 400"> <defs> <marker id="mCircle" markerWidth="10" markerHeiht="10"> <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-start:url(#mCircle);fill:none;stroke: black;"> </svg>
效果不完全符合预期。
圆形标记显示在错误的位置的原因是,默认情况下,开始标记的(0,0)点与路径的开始坐标对齐。可以添加refX和reyY属性指定哪个坐标(标记的坐标系统中)与路径的开始坐标对齐。一旦添加好,圆形标记位置正确
<!-- 圆形标记初试 --> <svg width="400px" height="400px" viewBox="0 0 400 400"> <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-start:url(#mCircle);fill:none;stroke: black;"> </svg>
尝试三个标记。添加了三角形标记并在路径的marker-end引用,然后可以添加箭头标记并在marker-mid中引用它。marker-mid会附加给除路径起点和终点以外的每个顶点。注意还设置了refX和refY属性,因此箭头较宽的一端能与中间的顶点对齐,而实心三角形的尖角与结束顶点对齐。
想要获取想要的结果,必须明确设置标记的orient属性为auto。这会让标记自动旋转来匹配路径的方向。(也可以指定度数,此时标记始终按照指定的度数旋转,)
<!-- 圆形标记初试 --> <svg width="400px" height="400px" viewBox="0 0 400 400"> <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" abd 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>
效果图:
另一个有用的属性是markerUnits。如果设置为strokeWidth,那么标记的坐标系统会被设定为单位等于笔画宽度。标记会与笔画宽度成正比,这是它的默认行为,通常也是我们想要的。如果设置这个属性为userSpaceOnUse,标记的坐标系统会被假定为和引用该标记的对象的坐标系统一样。不管笔画宽度为多少,标记都会保持相同的尺寸。
7.10 标记记录
如果想要路径的起点、中间和终点都使用相同的标记,无需指定所有的marker-start、marker-mid和marker-end属性。只需使用marker属性引用想要的标记即可。因此如果想要所有的顶点都有一个圆形标记。如下:
<!-- 为所有顶点使用一个标记 --> <svg width="400px" height="400px" viewBox="0 0 400 400"> <defs> <marker id="mCircle" markerWidth="10" markerHeight="10" refX="5" refX="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;" /> </svg>
还可以在<marker>元素上设置viewBox和preserveAspectRatio属性,以更好地控制它的显示效果。例如,如果使用viewBox定义网络,让(0,0)坐标位于标记的中心,我们可能想要使用这种方式,而不是使用refX和refY。
可以在<polygon>、<polyline>或者<line>元素以及<path>中引用<marker>
确保没有为标记定义自己作为丛书标记。用如下的CSS规则为所有的路径添加一个星形标记时,可能会发生这种情况。
path {marker:url(#star)}
如果id为star的<marker>元素中也有一个<path>,这个路径会无限循环地引用标记自身。为了防止这种情况发生,要添加一条CSS规则。说明不要给星形标记中的路径添加任何标记。
path{marker:url(#star)}
marker#star path{marker:none}