Skip to content

Latest commit

 

History

History
306 lines (204 loc) · 9.38 KB

路径.md

File metadata and controls

306 lines (204 loc) · 9.38 KB

路径

创建路径

上一篇中提到了Canvas默认只提供了绘制矩形的方法,那么如果我们要绘制其他形状要怎么办呢,这就需要使用本篇要介绍的路径。

路径就是一组由直线段或曲线段组成的形状,举个例子,路径就像我们画画时先用铅笔勾勒出的要画的物体的形状,有了形状就可以使用其他色彩笔进行填充或描边。

要开始创建一段路径,需要先调用beginPath方法:

ctx.beginPath()

然后调用moveTo方法将新创建的这段路径的起始点移到你想要的位置,就像你移动铅笔一样:

ctx.moveTo(20, 20)

接下来,如果你要画一条直线,那么就调用lineTo方法:

ctx.lineTo(30, 30)

这样就会从起点[20,20]创建一条到点[30,30]的路径,你可以继续调用任意次的lineTo方法绘制更多线段。

如果你想要紧接着创建一条曲线路径,那么可以使用二次或三次贝塞尔曲线。

二次贝塞尔曲线有一个控制点,可以通过如下方法在当前的路径中添加:

ctx.quadraticCurveTo(cpx, cpy, x, y)

前两个参数代表控制点,后两个参数代表终点,而起点是当前路径上最后的点。

添加三次贝赛尔曲线路径的方法为:

ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

不同之处在于多了一个控制点。

除了曲线还可以创建圆弧路径,提供了两个方法,第一个是通过圆心和半径来创建的:

ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)

第三、四个参数表示圆弧的起始和结束角度,从x轴正方向开始,单位为弧度,角度转弧度的公式如下:

弧度 = (Math.PI / 180) * 角度

最后一个参数是可选的,布尔值,默认为false,代表顺时针创建圆弧,设为true,则为逆时针。

这段圆弧的起点会和原有路径最后的点相连:

image-20231024143630336

第二个方法可能不太直观和常用,它是通过两个控制点及半径来创建圆弧:

ctx.arcTo(x1, y1, x2, y2, radius)

起点同样是当前路径的最后的点,然后起点和控制点1会形成一条直线,控制1和控制点2也会形成一条直线,然后就可以得到一个和这两条直线相切的指定半径的圆,最后圆上面在这两条直线之间的圆弧就是我们得到的:

image-20231024140524849

如图所示,创建出的就是红色部分的圆弧路径。

最后两个创建路径的方法是用于创建椭圆和矩形的,这里就一笔带过了:

ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
ctx.rect(x, y, width, height)

beginPath方法对应的还有一个closePath方法,如果你要绘制闭合的路径可以调用它,它会帮你把路径的最后一个点和第一个点相连:

ctx.closePath()

没有调用closePath

ctx.beginPath()
ctx.moveTo(100, 20)
ctx.lineTo(150, 60)
ctx.lineTo(50, 60)

image-20231024141401655

调用了closePath

// ...
ctx.closePath()

image-20231024141442059

所以如果你不需要路径闭合,那么可以省略。

如果你想创建一条新的路径,那么直接再次调用beginPath即可,建议绘制和原有路径不相关的新路径时最好都通过beginPath方法开始新路径,否则你的新路径很可能和之前的路径相连着。

绘制路径

单纯的创建路径并不会在画布上渲染出任何可见的东西,想要图形可见需要调用填充或描边方法。

填充或描边都只针对当前路径,所以如果你想给不同的图形应用不同的样式,需要使用beginPath方法开启新的路径。

填充

填充路径可以使用fill方法:

ctx.beginPath()
ctx.moveTo(100, 20)
ctx.lineTo(150, 60)
ctx.arc(50, 50, 50, Math.PI / 4, Math.PI)
ctx.closePath()

ctx.fillStyle = 'green'
ctx.fill()

image-20231024144434403

即使没有手动调用closePath闭合路径,fill方法也是有效的,它会自动进行闭合,你可以试试把ctx.closePath()去掉。

描边

描边路径可以使用stroke方法:

ctx.beginPath()
ctx.moveTo(100, 20)
ctx.lineTo(150, 60)
ctx.arc(50, 50, 50, Math.PI / 4, Math.PI)

ctx.strokeStyle = '#409eff'
ctx.stroke()

image-20231024145333412

线段样式

除了前面涉及到的strokeStyle属性用于描边颜色外,还有其他几个属性可以控制描边的样式。

  • 通过lineWidth 属性可以设置描边的线宽。

  • 通过lineJoin 属性可以设置不同的线段连接处的样式,有三个可选值:roundbevelmiter,默认为miter

  • 通过lineCap 属性可以设置线段末端的样式,也有三个可选值:buttroundsquare,默认为butt

  • 通过setLineDash方法和lineDashOffset 属性设置线段的虚线效果。

ctx.strokeStyle = '#409eff'
ctx.lineWidth = 5
ctx.lineJoin = 'round'
ctx.lineCap = 'square'
ctx.setLineDash([5, 10])
ctx.stroke()

image-20231024151113402

需要注意的是lineCapbutt外的另外两个属性都会在外绘制额外的形状:

image-20231024151207236

所以如果虚线的间隙值设置的太小的话有可能会让它变回实线:

ctx.lineCap = 'square'
ctx.setLineDash([5, 5])

image-20231024151343235

Path2D

前面我们创建路径都是通过canvas的上下文对象来直接创建的,这样创建的路径无法重用,你只能再次按顺序创建一遍,Path2D就是用来解决这个问题,我们通过调用Path2D的方法创建路径,然后就可以不断的调用填充或描边方法绘制这条路径。

Path2D是一个构造函数,所以需要先创建一个实例:

const path = new Path2D()

接收一个参数,可以是另一个Path2D实例, 也可以是SVG路径字符串。所以不但可以复用canvas的路径,还可以很轻松的直接复用SVG的路径。

然后前面介绍过的moveTobezierCurveToarc方法它都有,使用上也是完全一样,当然,还是有一点不同的:

1.没有beginPath方法,因为创建Path2D实例就相当于创建了一条新路径。

2.多了一个addPath方法,可以把其他Path2D路径添加到当前路径中。

const p1 = new Path2D()
p1.moveTo(80, 80)
p1.lineTo(120, 120)

const p2 = new Path2D()
p2.rect(50, 50, 120, 120)

p1.addPath(p2)

ctx.stroke(p1)

image-20231027104156425

状态保存和恢复

你可能会有这种需求,绘制第一个图形时,给它设置了一些填充或描边的样式,然后再绘制第二个图形时,你又想给它设置不同的样式,再绘制第三个图形时又想和第一个图形的样式一样,目前为止你需要这么做:

ctx.beginPath()
ctx.moveTo(100, 50)
ctx.lineTo(150, 60)
ctx.strokeStyle = '#409eff'
ctx.lineWidth = 5
ctx.setLineDash([5, 5])
ctx.stroke()

ctx.beginPath()
ctx.moveTo(150, 150)
ctx.lineTo(150, 80)
ctx.quadraticCurveTo(30, 40, 60, 70)
ctx.strokeStyle = '#FFC942'
ctx.lineWidth = 2
ctx.setLineDash([])
ctx.stroke()

ctx.beginPath()
ctx.arc(125, 125, 100, 0, Math.PI * 2)
ctx.strokeStyle = '#409eff'
ctx.lineWidth = 5
ctx.setLineDash([5, 5])
ctx.stroke()

image-20231026112658655

因为样式属性的设置会对后面所有的绘制操作都生效,所以你只能手动把之前的样式清除或修改,然后再设置或者恢复,上面只是几个图形,也许你还能接受,但是如果要绘制的图形数量更多,样式设置更加复杂,相信你很容易就迷失在这些状态中了,但是还好,canvas提供了两个方法用来帮我们保存状态和恢复状态,改写如下:

ctx.beginPath()
ctx.moveTo(100, 50)
ctx.lineTo(150, 60)
ctx.strokeStyle = '#409eff'
ctx.lineWidth = 5
ctx.setLineDash([5, 5])
ctx.stroke()

ctx.save()// ++

ctx.beginPath()
ctx.moveTo(150, 150)
ctx.lineTo(150, 80)
ctx.quadraticCurveTo(30, 40, 60, 70)
ctx.strokeStyle = '#FFC942'
ctx.lineWidth = 2
ctx.setLineDash([])
ctx.stroke()

ctx.restore()// ++

ctx.beginPath()
ctx.arc(125, 125, 100, 0, Math.PI * 2)
ctx.stroke()

save方法会将画布当前的状态保存到栈中,无论你接下来设置了什么状态,在调用restore方法后都会恢复到上一次的状态,因为是通过栈来保存,所以可以嵌套:

ctx.save()

// ...
ctx.save()

// ...
ctx.restore()

ctx.restore()

每次调用restore都会恢复到上一次的状态,也就是当前栈顶的状态。

建议每次在设置画布状态前都通过save方法来保存当前的状态。

总结

本节我们了解了一下路径的创建、绘制、重用以及状态的保存与恢复,现在你应该可以绘制出各种你想要的图形,并且使用各种样式对路径填充或描边,接下来会通过一些常见的实战案例来进一步加深了解。

本节示例地址:path