手写 SVG - 用简单指令画画

1,292 阅读10分钟

上一期我们了解了如何在 SVG 里使用一些简单的图形,但是为了创造更加复杂的图形,仅仅使用简单图形来拼凑会非常繁琐。

SVG 提供了一个路径绘制功能,使用简单的指令就可以绘制一个任意图形。

在 SVG 中,我们使用 <path> 标签来定义线条路径,它有一个 d 属性,用于定义绘制图形的指令。

SVG 的指令分为四类:移动绘制点、直线连接、曲线绘制、闭合,其中曲线绘制分为椭圆弧曲线、二次贝塞尔曲线、三次贝塞尔曲线三种。

所有指令均区分大小写,大小写影响参数指定的方式,大写指令表示后面的参数使用绝对坐标,而小写指令表示后面的参数使用相对于当前绘制点位置的相对坐标。

指令参数可直接跟在指令后面,多个参数可以使用英文逗号 , 或空格 进行分隔,指令和参数之间可以有空格也可以没有。

在不产生歧义的状态下,参数之间也可以没有 , 间隔。比如两个参数时,通常要写成 1 2 或是 1,2,但是如果第二个参数是负数,则可以省略间隔,写成 1-2;再比如下面会提到的椭圆弧曲线指令的第 4、5 个参数取值只能是 01,因此它们之间也不需要间隔,比如椭圆弧 A 50 60 20 0 1 200 200 的第 4、5 两个参数可以直接写成01,甚至可以直接和后面的参数连接得到 A50 60 20 01200 200。当然,这在正常情况下是不推荐的,手写的话还是尽可能使用逗号 , 和空格 把参数区分清楚比较好,但是在做 SVG 压缩的时候则可以删除这些间距。

各命令简介

移动绘制点指令为 Mm,接收横、纵坐标两个参数,使用这个指令可以将绘制点移动到指定的坐标点。

直线连接指令有六个,分别为大写 LHV 和它们的小写。其中 Ll 指令接收横、纵坐标两个参数,表示从当前绘制点坐标开始绘制一条直线到目标坐标点,并将绘制点移动到目标坐标点;Hh 指令接收横坐标一个参数,表示从当前绘制点坐标开始绘制一条水平直线,保持纵坐标不变,连接到目标横坐标点,并将绘制点移动到终点;Vv 指令接收纵坐标一个参数,表示从当前绘制点坐标开始绘制一条竖直直线,保持横坐标不变,连接到目标纵坐标点,并将绘制点移动到终点。

<svg
     xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 500 500"
     width="500"
     height="500"
     style="border: 1px solid red"
>
  <path
        d="M100,100L200,250l-50 50V400H450v-80h-20"
        fill="none"
        stroke="green"
  />
</svg>

曲线绘制比较复杂,包含椭圆弧曲线、二次贝塞尔曲线和三次贝塞尔曲线。

椭圆弧曲线指令为 Aa,接收七个参数。第 1、2 两个参数根据大小分别表示半长轴和半短轴长度;第 3 个参数为椭圆的旋转角度,正数为顺时针旋转,负数为逆时针旋转;第 4 个参数只有 01 两种取值,指定截取椭圆的优弧还是劣弧,1 表示截取优弧,0 表示截取劣弧;第 5 个参数也只有 01 两种取值,指定椭圆绘制方向,1 表示顺时针绘制,0 表示逆时针绘制;第 6、7 两个参数接收横、纵坐标表示曲线终点;根据这七个参数绘制出一条从指定椭圆上截取下来的椭圆弧曲线后,并将绘制点移动到终点。

<svg
     xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 500 500"
     width="500"
     height="500"
     style="border: 1px solid red"
>
  <path
        d="
            M100,100
            L200,250 l-50 50
            V400H450
            v-80h-20
            A50,60 45 0 1 350,350
            a80 70 90 1 0 0-200
        "
        fill="none"
        stroke="green"
  />
</svg>

二次贝塞尔曲线指令有四个,分别为大写 QT 和它们的小写。其中 Qq 指令接收 4n 个参数(n 为正整数),也就是可以直接重复提供参数,每次提供 4 个,四个参数前两个参数接收横、纵坐标表示二次贝塞尔曲线的控制点坐标,后两个参数接收横、纵坐标表示终点坐标,然后从当前绘制点开始根据控制点、终点绘制一条二次贝塞尔曲线,并将绘制点移动到目标坐标点,后续可以继续提供 4 个参数从当前终点开始继续绘制二次贝塞尔曲线,并将绘制点移动到最后一组参数的终点坐标点处;Tt 指令接收 2n 个参数(n 为正整数),同样是可以直接重复提供参数的,每次提供 2 个,参数接收横、纵坐标,表示终点坐标,控制点为前一次二次贝塞尔曲线绘制命令的控制点关于当前绘制点的对称点(若是第一个二次贝塞尔曲线绘制命令,则控制点与当前绘制点相同),然后从当前绘制点开始根据控制点、终点绘制一条二次贝塞尔曲线,并将绘制点移动到最后一组参数的终点坐标点处,后续可以继续提供 2 个参数从当前终点开始继续绘制二次贝塞尔曲线,并将绘制点移动到最后一组参数的终点坐标点处。

<svg
     xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 500 500"
     width="500"
     height="500"
     style="border: 1px solid red"
>
  <path
        d="
            M 50,250
            L150,100 L250,250
            l100 150 l100-150
            M 50,250
            Q150,100  250,250
            q100 150  200   0
        "
        fill="none"
        stroke="green"
  />
  <path
        d="
            M 50,250
            q 50 100 100   0
            Q200,150 250,250
            T350,250
            t100   0
        "
        fill="none"
        stroke="blue"
  />
  <path
        d="
            M 50,250
            Q 75,200 100,250 125,300 150,250
            q 25 -50  50   0  25  50  50   0
            T300,250 350,250
            t 50   0  50   0
        "
        fill="none"
        stroke="orange"
  />
</svg>

三次贝塞尔曲线指令也有四个,分别为大写 CS 和它们的小写,接收参数的方式与二次贝塞尔曲线指令类似,只不过三次贝塞尔曲线有两个控制点,所以参数都会多一个坐标对。Cc 指令接收 6n 个参数(n 为正整数),6 个参数分别为第一个控制点的横、纵坐标,第二个控制点的横、纵坐标和终点的横、纵坐标,规则与二次贝塞尔曲线一样,可以 6 个一组地重复提供,根据起点、终点和两个控制点绘制一条三次贝塞尔曲线,并将绘制点移动到最后一组参数的终点坐标点处;Ss 指令接收 4n 个参数(n 为正整数),4 个参数分别为第二个控制点的横、纵坐标和终点的横、纵坐标,第一个控制点的坐标为前一次三次贝塞尔曲线绘制命令的第二个控制点关于当前绘制点的对称点(若是第一个三次贝塞尔曲线绘制命令,则第一个控制点与当前绘制点相同),然后规则与二次贝塞尔曲线一样,可以 4 个一组地重复提供,根据起点、终点和两个控制点绘制一条三次贝塞尔曲线,并将绘制点移动到最后一组参数的终点坐标点处。

<svg
     xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 500 500"
     width="500"
     height="500"
     style="border: 1px solid red"
>
  <path
        d="
            M 50,250
            L  0,100 L300,100 L250,250
            l-50 150 l300   0 l-50-150
            M 50,250
            C  0,100  300,100  250,250
            c-50 150  250 150  200   0
        "
        fill="none"
        stroke="green"
  />
  <path
        d="
            M 50,250
            c-40 100 140 100 100   0
            C110,150 290,150 250,250
            S390,350 350,250
            s140-100 100   0
        "
        fill="none"
        stroke="blue"
  />
  <path
        d="
            M 50,250
            C 30,200 120,200 100,250  80,300 170,300 150,250
            c-20 -50  70 -50  50   0 -20  50  70  50  50   0
            S320,200 300,250 370,300 350,250
            s 70 -50  50   0  70  50  50   0
        "
        fill="none"
        stroke="orange"
  />
</svg>

闭合指令为 Zz,二者没有区别,不接收参数,功能与直线连接指令类似,将会从当前绘制点开始绘制一条直线到最近的 Mm 指令指定的坐标点,闭合绘制的图形,并将绘制点移动到终点。

<svg
     xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 500 500"
     width="500"
     height="500"
     style="border: 1px solid red"
>
  <path
        d="
            M100,100
            L200,250 l-50 50
            V400H450
            v-80h-20
            A50,60 45 0 1 350,350
            a80 70 90 1 0 0-200
            Z
            M340,240
            a10 10 0 0 1 20 0
            a10 10 0 0 1 20 0
            q0 15 -20 30
            q-20 -15 -20-30
            m20 -30
            l40 40 l-40 40 l -40 -40
            z
        "
        fill="none"
        stroke="green"
  />
</svg>

有了这 20 个区分大小的指令之后,就可以绘制出任意形状了,就像拿着一支画笔在纸上进行绘制一样。<path> 标签同样支持 fillstroke 属性,用于指定填充色和描边颜色。

由于曲线绘制命令比较复杂,通常不会直接手写,因为那样并不直观,所以大部分情况下,手写 SVG 只需要 MLHVZ 和它们的小写版本就足够了,如果是涉及曲线的图形,则使用 <circle><ellipse> 来绘制,复杂的情况最好还是交给 GUI 工具去绘制。


至此,我们已经可以用 SVG 画出任意图形图案了,那么下一期,我们可以来看看 SVG 中动画的制作~