二次贝塞尔曲线;
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 给定的点数组
const points = [
{ x: 50, y: 100 },
{ x: 100, y: 200 },
{ x: 150, y: 50 },
{ x: 200, y: 250 },
{ x: 250, y: 150 }
]
// 设置起始点
ctx.beginPath()
ctx.moveTo(points[0].x, points[0].y)
// 使用贝塞尔曲线穿过每一个点
for (let i = 1; i < points.length; i++) {
const cpx = (points[i].x + points[i - 1].x) / 2 // 控制点的 x 坐标
const cpy = (points[i].y + points[i - 1].y) / 2 // 控制点的 y 坐标
ctx.quadraticCurveTo(points[i - 1].x, points[i - 1].y, cpx, cpy)
}
// 绘制曲线
ctx.lineWidth = 2
ctx.strokeStyle = 'blue'
ctx.stroke()
for (let i = 0; i < points.length - 1; i++) {
ctx.beginPath()
// 绘制小圆
ctx.strokeStyle = 'red'
ctx.arc(points[i].x, points[i].y, 5, 0, Math.PI * 2, false)
ctx.stroke() // 开始画
// 绘制大圆
// 绘制大圆
ctx.beginPath()
ctx.strokeStyle = 'red' // 半透明蓝色背景
ctx.arc(points[i].x, points[i].y, 20, 0, Math.PI * 2, false)
ctx.stroke() // 开始画
}
ctx.closePath() // 关闭路径
效果如下
可以看到效果是很平滑,但无法穿过对应的每一个点;
三次贝塞尔曲线
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 给定的点数组
const points = [
{ x: 50, y: 100 },
{ x: 100, y: 200 },
{ x: 150, y: 50 },
{ x: 200, y: 250 },
{ x: 250, y: 150 }
]
// 设置起始点
ctx.beginPath()
ctx.moveTo(points[0].x, points[0].y)
// 绘制曲线
for (let i = 1; i < points.length - 1; i++) {
const p0 = i === 1 ? points[0] : points[i - 1]
const p1 = points[i]
const p2 = points[i + 1]
const p3 = i === points.length - 2 ? points[points.length - 1] : points[i + 2]
const cp1x = p1.x + (p2.x - p0.x) / 5
const cp1y = p1.y + (p2.y - p0.y) / 5
const cp2x = p2.x - (p3.x - p1.x) / 5
const cp2y = p2.y - (p3.y - p1.y) / 5
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y)
}
ctx.stroke() // 开始画
// 绘制给定的点
ctx.fillStyle = 'blue'
for (let i = 0; i < points.length; i++) {
ctx.beginPath()
ctx.arc(points[i].x, points[i].y, 4, 0, 2 * Math.PI)
ctx.fill()
}
效果如下
可以看到无法穿过第一个点,其余都很完美;
所以我们对曲线绘制的逻辑进行修改,第一个点和第二个点之间,而其他点之间使用三次贝塞尔曲线。这样的修改可以确保曲线平滑,并且所有点都在曲线上;
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 给定的点数组
const points = [
{ x: 50, y: 100 },
{ x: 100, y: 200 },
{ x: 150, y: 50 },
{ x: 200, y: 250 },
{ x: 250, y: 150 }
]
// 设置起始点
ctx.beginPath()
ctx.moveTo(points[0].x, points[0].y)
const p0 = points[0]
const p1 = points[1]
const p2 = points[2]
const cp1x = p0.x + (p1.x - p0.x) / 2
const cp1y = p0.y + (p1.y - p0.y) / 2
ctx.quadraticCurveTo(cp1x, cp1y, p1.x, p1.y)
// 绘制曲线
for (let i = 1; i < points.length - 1; i++) {
const p0 = i === 1 ? points[0] : points[i - 1]
const p1 = points[i]
const p2 = points[i + 1]
const p3 = i === points.length - 2 ? points[points.length - 1] : points[i + 2]
const cp1x = p1.x + (p2.x - p0.x) / 5
const cp1y = p1.y + (p2.y - p0.y) / 5
const cp2x = p2.x - (p3.x - p1.x) / 5
const cp2y = p2.y - (p3.y - p1.y) / 5
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y)
}
ctx.stroke() // 开始画
// 绘制给定的点
ctx.fillStyle = 'blue'
for (let i = 0; i < points.length; i++) {
ctx.beginPath()
ctx.arc(points[i].x, points[i].y, 4, 0, 2 * Math.PI)
ctx.fill()
}
ctx.closePath() // 关闭路径
其实也尝试过不同的算法
function getCtrlPoint(ps: any[], i: number, a = 0.1, b = 0.3) {
var distX = ps[i].x - ps[i - 1].x
var distY = ps[i].y - ps[i - 1].y
var distance = Math.sqrt(distX * distX + distY * distY)
var distCompX = distX / distance
var distCompY = distY / distance
var pAx = ps[i - 1].x + distX * distCompX * a
var pAy = ps[i - 1].y + distY * distCompY * a
var pBx = ps[i].x - distX * distCompX * b
var pBy = ps[i].y - distY * distCompY * b
return {
pA: { x: Math.round(pAx), y: Math.round(pAy) },
pB: { x: Math.round(pBx), y: Math.round(pBy) },
}
}
但输出的曲线都很奇怪;