使用svg path标签绘制贝塞尔曲线

2,077 阅读3分钟

在svg中,所有的图形都可以用path来模拟实现,在MDN可以找到一篇入门教程

绘制贝塞尔曲线的要点

从三次贝塞尔曲线入手,三次贝塞尔曲线需要一个坐标点和两个控制点,具体体现在标签path中就是d属性

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

其中x y是最终的终点,x1 y1,x2 y2是两个控制点的坐标。这样写有点抽象,我们来举个例子

这是一段贝塞尔曲线,它是由path标签产生,d属性为

M 162.5 338.5 C 165.5 230.5 301.5 223.5 290.5 292.5

它的起始点坐标为(162.5, 338.5),M表示的是move to,之后的C字母表示curve,也就是一段三次贝塞尔曲线。可以看成

C 控制点1(x, y) 控制点2(x, y) 终点(x, y)

绘制这一段贝塞尔曲线,使用path标签只需要知道坐标点和对应控制点的位置即可

数据结构

实际上这里的数据结构我们只需要存储坐标点和控制点,以及一些公共的属性

function path{
  nodes: Node[],
  strokeWidth: number,
  stroke: string,
  fill: string
}

function node {
  anchorPoint: {
      x,
      y  //锚点的坐标
  },
  ctrPoint: {
      x,
      y //控制点的坐标
  }
}

一段path由多个锚点构成,故在path结构中添加了一个锚点的数组,用以存储点的坐标

渲染path

这里的工作有些像react的vdom,使用JavaScript来描述path的属性和各个点的位置,通过计算获取到d属性(一个path的字符串),交给浏览器去进行渲染,而不是直接操作DOM,维护和修改更加方便

const d = nodes.reduce((pre, cur, index) => {
	if (index === 0) {
   		pre += `M ${cur.anchorPoint.x} ${cur.anchorPoint.y} C ${cur.ctrPoint.x} ${cur.ctrPoint.y}`
     	} else {
        	pre += `${cur.ctrPoint.x} ${cur.ctrPoint.y} C ${cur.anchorPoint.x} ${cur.anchorPoint.y}`
        }
    return pre
}, "")

需要将各个点的坐标组合成浏览器可以识别的D属性,之后交给浏览器去做渲染的工作

多段贝塞尔曲线

在实际中,很少会出现只有一段贝塞尔曲线的情况,例如在photoShop中,钢笔工具可以绘制多段贝塞尔曲线,幸运的是,path标签为我们提供了这样功能。

M 222.5 485.5 C 222.5 485.5 350.5 561.5 359.5 453.5 C 368.5 345.5 445.5 431.5 445.5 431.5

实际上只要不断添加C命令,就可以完成多段贝塞尔曲线的绘制,需要注意的是,两个C之间的交界点,锚点坐标相同而控制点坐标关于锚点中心对称

那么需要修改一下node的数据结构,添加第二控制点



function node {
  anchorPoint: {
      x,
      y  //锚点的坐标
  },
  ctrPoint: {
      x,
      y //控制点的坐标
  }
  ctr2Point: {
      x,
      y
  }
}

同样修改一下D的计算方法

const d = nodes.reduce((pre, cur, index) => {
	if (index === 0) {
   		pre += `M ${cur.anchorPoint.x} ${cur.anchorPoint.y} C ${cur.ctrPoint.x} ${cur.ctrPoint.y}` // 起始节点
     	} else if (index + 1 < nodes.length - 1) {
        	pre += `${nodes[i].ctrPosX} ${nodes[i].ctrPosY} ${nodes[i].posX} ${nodes[i].posY} C ${nodes[i].ctr2PosX} ${nodes[i].ctr2PosY} ` //中间节点
        }
        else {
        	pre += `${cur.ctrPoint.x} ${cur.ctrPoint.y} C ${cur.anchorPoint.x} ${cur.anchorPoint.y}` //末尾节点
        }
    return pre
}, "")

这样就完成了多段贝塞尔曲线的绘制