n次贝塞尔曲线的递归公式:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.translate(10, 10); // 重新映射画布上的 (0,0) 位置,避免溢出
function drawCircle(x, y, color, r = 6) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
}
function drawPath(path) {
ctx.beginPath();
path.forEach((p, i) => {
if (i) {
ctx.lineTo(p.x, p.y);
} else {
ctx.moveTo(p.x, p.y);
}
});
ctx.stroke();
}
function bezier(pointList, t) {
if (pointList.length === 1) {
return pointList[0];
} else {
// B(t)=(1-t)Bp0p1…pn-1(t) + tBp1p2…pn(t)
var p1 = bezier(pointList.slice(0, -1), t);
var p2 = bezier(pointList.slice(1), t);
var p = {
x: (1 - t) * p1.x + t * p2.x,
y: (1 - t) * p1.y + t * p2.y,
};
return p;
}
}
var startPoint = { x: 0, y: 0 };
var endPoint = { x: 900, y: 0 };
var pointList = [
startPoint,
// 中间为控制点
{ x: 300, y: 0 },
{ x: 400, y: 100 },
{ x: 600, y: 200 },
{ x: 700, y: 0 },
endPoint,
];
// 绘制曲线
var path = [];
for (var i = 0; i <= 100; i++) {
path.push(bezier(pointList, i / 100));
}
drawPath(path);
// 红色起始结束点
[startPoint, endPoint].forEach((p) => {
drawCircle(p.x, p.y, "red");
});
// 蓝色控制点
pointList.slice(1, -1).forEach((p) => {
drawCircle(p.x, p.y, "blue");
});
最终效果