三次贝塞尔曲线比二次贝塞尔曲线更灵活,使用两个控制点来定义曲线形状。它可以绑制S形曲线和更复杂的曲线。
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
| 参数 | 类型 | 说明 |
|---|---|---|
| cp1x, cp1y | number | 第一个控制点坐标 |
| cp2x, cp2y | number | 第二个控制点坐标 |
| x, y | number | 终点坐标 |
三次贝塞尔曲线由四个点定义:
起点 ────┐
│
控制点1
│
│
控制点2
│
│
终点
曲线从起点出发,受两个控制点影响,最终到达终点。
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.bezierCurveTo(150, 50, 250, 250, 350, 150)
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 3
ctx.stroke()
起点: (50, 180)
控制点1: (120, 30)
控制点2: (330, 250)
终点: (400, 180)
两个控制点共同决定曲线形状:
// S形曲线
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.bezierCurveTo(150, 50, 250, 250, 350, 150)
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 3
ctx.stroke()
// 平滑曲线
ctx.beginPath()
ctx.moveTo(50, 200)
ctx.bezierCurveTo(150, 100, 250, 100, 350, 200)
ctx.strokeStyle = '#2ecc71'
ctx.lineWidth = 3
ctx.stroke()
使用三次贝塞尔曲线绑制心形:
function drawHeart(ctx, x, y, size) {
ctx.beginPath()
ctx.moveTo(x, y + size * 0.3)
ctx.bezierCurveTo(
x, y - size * 0.5,
x - size, y - size * 0.5,
x - size, y + size * 0.1
)
ctx.bezierCurveTo(
x - size, y + size * 0.8,
x, y + size * 1.2,
x, y + size * 1.5
)
ctx.bezierCurveTo(
x, y + size * 1.2,
x + size, y + size * 0.8,
x + size, y + size * 0.1
)
ctx.bezierCurveTo(
x + size, y - size * 0.5,
x, y - size * 0.5,
x, y + size * 0.3
)
ctx.closePath()
}
drawHeart(ctx, 200, 100, 50)
ctx.fillStyle = '#e74c3c'
ctx.fill()
将任意点集转换为平滑曲线:
const points = [
{ x: 50, y: 200 },
{ x: 120, y: 100 },
{ x: 200, y: 180 },
{ x: 280, y: 80 },
{ x: 350, y: 150 },
{ x: 450, y: 120 }
]
function drawSmoothPath(ctx, points) {
if (points.length < 2) return
ctx.beginPath()
ctx.moveTo(points[0].x, points[0].y)
for (let i = 1; i < points.length - 1; i++) {
const prev = points[i - 1]
const curr = points[i]
const next = points[i + 1]
const cp1x = prev.x + (curr.x - prev.x) / 3
const cp1y = prev.y + (curr.y - prev.y) / 3
const cp2x = curr.x - (next.x - prev.x) / 3
const cp2y = curr.y - (next.y - prev.y) / 3
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, curr.x, curr.y)
}
ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y)
ctx.stroke()
}
drawSmoothPath(ctx, points)
ctx.strokeStyle = '#9b59b6'
ctx.lineWidth = 3
ctx.stroke()
1个控制点
2个控制点
控制点不在曲线上
// 两个控制点都不在曲线上
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.bezierCurveTo(150, 50, 250, 250, 350, 150)
// 曲线不会经过控制点
ctx.stroke()
起点很重要
// 必须先moveTo
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.bezierCurveTo(150, 50, 250, 250, 350, 150)
ctx.stroke()
// 如果没有moveTo,从(0,0)开始
ctx.beginPath()
ctx.bezierCurveTo(150, 50, 250, 250, 350, 150)
ctx.stroke()
控制点重合时
// 当两个控制点重合时,效果类似二次贝塞尔曲线
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.bezierCurveTo(200, 50, 200, 50, 350, 150)
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 3
ctx.stroke()
连续曲线
// 多个曲线可以连续
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.bezierCurveTo(100, 50, 150, 250, 200, 150)
ctx.bezierCurveTo(250, 50, 300, 250, 350, 150)
ctx.strokeStyle = '#2ecc71'
ctx.lineWidth = 3
ctx.stroke()