深入学习和实践 SVG Path

1,449 阅读7分钟

背景

最近在使用 G6 画脑图自定义的一些边、线时候,发现对 SVG Path (路径)的一些知识还有些生涩,做个自我知识总结。

SVG 介绍

SVG是一种 [XML] 语言,类似 XHTML,可以用来绘制矢量图形,例如下图展示的图形。SVG可以通过定义必要的线和形状来创建一个图形,也可以修改已有的位图,或者将这两种方式结合起来创建图形。图形和其组成部分可以形变(be transformed)、合成、或者通过滤镜完全改变外观。

GIF 2022-3-19 15-02-01.gif

还可以通过 <animateTransform>标签来制作动图, 如上面的 loading

<svg version="1.1" id="L9" xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 0 0"
  xml:space="preserve">
  <path fill="#fff"
    d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
    <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s" from="0 50 50"
      to="360 50 50" repeatCount="indefinite" />
  </path>
</svg>

基本元素介绍

SVG 提供了一些元素,用于定义圆形、矩形、一些简单或者复杂的曲线;一个简单的 SVG 文档由<svg>根元素和基本的形状元素构成。另外还有一个g元素,它用来把若干个基本形状编成一个组。

从这些开始,SVG 可以变得更加复杂。SVG 还可以支持渐变、旋转、动画、滤镜效果、与 JavaScript 交互等等功能,但是所有这些额外的语言特性,都需要在一个定义好的图形区域内实现。

浏览器兼容性

所有的现代浏览器都支持SVG,Can I use上有一份比较详细的支持SVG的浏览器列表,如下图。

impicture_20220319_141252.png

Path

重点介绍 SVG 的 Path

path 可能是 SVG 中最常见的形状,你可以用 path 元素绘制矩形(直角矩形或者圆角矩形)、折线、圆形、椭圆、多边形,以及一些其他的复杂形状,例如贝塞尔曲线、2次曲线等曲线。path 很强大也比较复杂,path 只需要设定很少的点,就可以创建平滑流畅的线条(比如曲线)。虽然polyline元素也能实现类似的效果,但是必须设置大量的点(点越密集,越接近连续,看起来越平滑流畅),并且这种做法不能够放大(放大后,点的离散更明显)。

标签

标签用来定义路径。

下面的命令可用于路径数据:

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Belzier curve
  • T = smooth quadratic Belzier curveto
  • A = elliptical Arc
  • Z = closepath

ps:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。

画基础图形

首先是“Move to”命令,M,前面已经提到过,它需要两个参数,分别是需要移动到的点的x轴和y轴的坐标。假设,你的画笔当前位于一个点,在使用M命令移动画笔后,只会移动画笔,但不会在两点之间画线。因为M命令仅仅是移动画笔,但不画线。所以M命令经常出现在路径的开始处,用来指明从何处开始画。

M x y

m dx dy

“Line to”命令,LL需要两个参数,分别是一个点的x轴和y轴坐标,L命令将会在当前位置和新位置(L前面画笔所在的点)之间画一条线段。

 L x y (or l dx dy)

另外还有两个简写命令,用来绘制水平线和垂直线。H,绘制水平线。V,绘制垂直线。这两个命令都只带一个参数,标明在x轴或y轴移动到的位置,因为它们都只在坐标轴的一个方向上移动。

画个简单的矩形:

<?xml version="1.0" standalone="no"?>
<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 10 H 90 V 90 H 10 L 10 10" />

  <circle cx="10" cy="10" r="5" fill="blue" />
  <circle cx="90" cy="90" r="5" fill="blue" />
  <circle cx="90" cy="10" r="5" fill="blue" />
  <circle cx="10" cy="90" r="5" fill="blue" />
</svg>

impicture_20220319_154305.png

画曲线

绘制平滑曲线的命令有三个,其中两个用来绘制贝塞尔曲线,另外一个用来绘制弧形或者说是圆的一部分; 贝塞尔曲线的类型有很多,但是在 path 元素里,只存在两种贝塞尔曲线:三次贝塞尔曲线 C,和二次贝塞尔曲线 Q。

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。

三次贝塞尔曲线 C

该曲线需要定义一个点和两个控制点,所以用 C 命令创建三次贝塞尔曲线,需要设置三组坐标参数:

 C x1 y1, x2 y2, x y (or c dx1 dy1, dx2 dy2, dx dy)

这里的最后一个坐标(x,y)表示的是曲线的终点,另外两个坐标是控制点,(x1,y1)是起点的控制点,(x2,y2)是终点的控制点。

你可以将若干个贝塞尔曲线连起来,从而创建出一条很长的平滑曲线。通常情况下,一个点某一侧的控制点是它另一侧的控制点的对称(以保持斜率不变)。这样,你可以使用一个简写的贝塞尔曲线命令 S,如下所示:

 S x2 y2, x y (or s dx2 dy2, dx dy)

S 命令可以用来创建与前面一样的贝塞尔曲线,但是,如果 S 命令跟在一个C或S命令后面,则它的第一个控制点会被假设成前一个命令曲线的第二个控制点的中心对称点。如果 S 命令单独使用,前面没有 C 或 S 命令,那当前点将作为第一个控制点。下面是 S 命令的语法示例,图中左侧红色标记的点对应的控制点即为蓝色标记点。

<?xml version="1.0" standalone="no"?>
<svg width="190px" height="160px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80" stroke="black" fill="transparent" />
</svg>

image.png

应用例子类似脑图的关系线(联系): impicture_20220319_155918.png

codePen 上有一些例子: codepen.io/thebabydino…

impicture_20220319_162134.png

二次贝塞尔曲线 Q 它比三次贝塞尔曲线简单,只需要一个控制点,用来确定起点和终点的曲线斜率。因此它需要两组参数,控制点和终点坐标。

 Q x1 y1, x y (or q dx1 dy1, dx dy)

代码示例如下:

<svg viewBox='-10 0 220 200' width='200' height='200' style='border: 1px solid blue'>
  <path d='M0 100, Q100 0 200 100 ' stroke='red' fill='transparent' />
</svg>

impicture_20220319_163507.png

像三次贝塞尔曲线有一个S命令,二次贝塞尔曲线有一个差不多的T命令,可以通过更简短的参数,延长二次贝塞尔曲线。

 T x y (or t dx dy)

impicture_20220319_164009.png

弧形

弧形命令 A 是另一个创建 SVG 曲线的命令。基本上,弧形可以视为圆形或椭圆形的一部分。 假设,已知椭圆形的长轴半径和短轴半径,并且已知两个点(在椭圆上),根据半径和两点,可以画出两个椭圆,在每个椭圆上根据两点都可以画出两种弧形。所以,仅仅根据半径和两点,可以画出四种弧形。为了保证创建的弧形唯一,A命令需要用到比较多的参数:

A rx ry x-axis-rotation large-arc-flag sweep-flag x y
a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy

G6 的自定义边相关

直接看 G6 的源码,可以看到里边自定义边代码,以 quadratic 类型来看 官方文档地址antv-g6.gitee.io/zh/docs/man…

Shape.registerEdge(
'quadratic',
  {
    curvePosition: 0.5, // 弯曲的默认位置
    curveOffset: -20, // 弯曲度,沿着 startPoint, endPoint 的垂直向量(顺时针)方向,距离线的距离,距离越大越弯曲
    getControlPoints(cfg: EdgeConfig): IPoint[] {
      let { controlPoints } = cfg; // 指定controlPoints
      if (!controlPoints || !controlPoints.length) {
        const { startPoint, endPoint } = cfg;
        if (cfg.curveOffset === undefined) cfg.curveOffset = this.curveOffset;
        if (cfg.curvePosition === undefined) cfg.curvePosition = this.curvePosition;
        if (isArray(this.curveOffset)) cfg.curveOffset = cfg.curveOffset[0];
        if (isArray(this.curvePosition)) cfg.curvePosition = cfg.curveOffset[0];
        const innerPoint = getControlPoint(
          startPoint as Point,
          endPoint as Point,
          cfg.curvePosition as number,
          cfg.curveOffset as number,
        );
        controlPoints = [innerPoint];
      }
      return controlPoints;
    },
    getPath(points: Point[]): Array<Array<string | number>> {
      const path = [];
      path.push(['M', points[0].x, points[0].y]);
      path.push(['Q', points[1].x, points[1].y, points[2].x, points[2].y]);
      return path;
    },
  },
  'single-edge',
);

可以看到使用了 M 和 二次贝塞尔曲线 Q; 可以类似像上述源码那样,去自定义拓展我们需要的边; 再结合 graph.updateItem() 就可以动态的去更改节点的边。

有补充的话再补充...