canvas中贝塞尔曲线函数
在《深入理解路径(Path)》中,有提到了贝塞尔曲线的相关函数,如下所示:
- context.quadraticCurveTo(cpx, cpy, x, y)
添加指定点(x,y)到当前子路径,该点和前一个点通过指定控制点(cpx, cpy)控制的二次贝塞尔曲线连接起来。
- context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
添加指定点(x,y)到当前子路径,该点和前一个点通过指定控制点(cp1x, cp1y)和(cp2x, cp2y)控制的三次贝塞尔曲线连接起来。
下面的示例绘制一条二次贝赛尔曲线:
ctx.save();
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.quadraticCurveTo(200, 100, 200, 200);
ctx.stroke();
arc(100,100);
arc(200, 100);
arc(200, 200);
ctx.restore();
首先画笔移动到点(100,100), 然后调用二次贝赛尔曲线路径方法:
ctx.quadraticCurveTo(200, 100, 200, 200);
控制点为(200,100),终点为(200,200)。
后面的arc方法是绘制一个小圆形,示例中在曲线的端点和控制点处绘制了小圆,用于示意端点和控制点的位置。
绘制的效果如下:

二次贝塞尔曲线
下面的示例绘制一条三次贝赛尔曲线:
ctx.save();
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.bezierCurveTo(200, 100, 200, 200,300,200);
ctx.stroke();
arc(100, 100);
arc(200, 100);
arc(200, 200);
arc(300, 200);
ctx.restore();
首先画笔移动到点(100,100), 然后调用三次贝赛尔曲线路径方法:
ctx.bezierCurveTo(200, 100, 200, 200,300,200);
控制点为(200,100)和(200,200),终点为(300,200)。
后面的arc方法是绘制一个小圆形,示例中在曲线的端点和控制点处绘制了小圆,用于示意端点和控制点的位置。
绘制的效果如下:

三次贝塞尔曲线
二次贝赛尔曲线
从前面的示例中,可以看出二次贝赛尔曲线包含一个控制点和两个端点。
提示:二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来指定开始点。
下图标注出了二次贝塞尔曲线的端点和控制点:

二次贝塞尔曲线-标注点
三次贝赛尔曲线
从前面的示例中,可以看出二次贝赛尔曲线包含两个控制点和两个端点。
提示:三次贝塞尔曲线需要三个点。前两个点是用于三次贝塞尔计算中的控制点,第三个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来定义开始点。
下图标注出了三次次贝塞尔曲线的端点和控制点:

三次贝塞尔曲线-标注点
曲线动画
本节源代码参考:animate.html和animate.js与iter.html和iter.js
动画需要计算曲线的任意中间点位,对于直线而言,任意点位都是比较容易计算的,用插值算法很方便就能计算出来。而曲线就不是那么容易了,要计算曲线的中间点位,需要首先理解曲线的原理。此处首先说明贝赛尔曲线的数学方程。
贝塞尔曲线的方程
贝塞尔曲线(Bezier curve)是计算机图形学中相当重要的参数曲线,贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所发表,他第一个研究了这种矢量绘制曲线的方法,并运用贝塞尔曲线来为汽车的主体进行设计。
贝塞尔曲线(Bezier curve)通过一个方程来描述一条曲线,根据方程的最高阶数,又分为线性贝赛尔曲线,二次贝塞尔曲线、三次贝塞尔曲线和更高阶的贝塞尔曲线。
贝塞尔曲线的定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。
一次贝塞尔曲线方程
一次贝塞尔曲线,其实就是线段。假设其起始点P0,终止点为P1,线段没有控制点。一次贝塞尔曲线的表达方程:
B(t) = (1 - t) * P0 + t * P1
其中: $t \in $[0,1]
意义:由 P0 至 P1 的连续点, 描述的一条线段。该方程描述了t从0变化到1,线段上的所有点的集合。下图形象表达了上述方程:

线段
二次贝塞尔曲线方程
二次贝塞尔曲线包括了起始点P0,终止点P2,和一个控制点P1。二次贝塞尔曲线的表达方程如下:
B(t) = (1-t)2 * P0 + 2t(1-t) * P1 + t2 * P2
其中: $t \in $[0,1]
三次贝塞尔曲线方程
三次贝塞尔曲线包括了其实点P0,终止点P3,和两个控制点P1和P2。三次贝塞尔曲线的表达方程如下:
B(t) = (1 - t)3 * P0 + 3t(1-t)2 * P1 + 3t2(1-t) * P2 + t3 * P3
其中: $t \in $[0,1]
更高阶贝塞尔曲线
一般来说,最常用的是二次和三次贝塞尔曲线。但是也可能会用到更高阶的贝塞尔曲线, 此处列出贝塞尔曲线的通用公式:

动画示例
本示例会演示如下动画: 一个圆点在一条二次曲线上面从起点移动到终止点。效果如下:

曲线动画
代码如下:
function draw() {
let p0 = {x:100,y:100},
p1 = {x:200,y:100},
p2 = {x:200,y:200};
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.strokeStyle = 'red';
arc(p0,p1,p2);
ctx.restore();
ctx.save();
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y);
ctx.stroke();
ctx.restore();
let pb = {};
pb.x = computeCurvePoint(p0.x,p1.x,p2.x,t);
pb.y = computeCurvePoint(p0.y,p1.y,p2.y,t);
ctx.save();
ctx.strokeStyle = 'green';
ctx.lineWidth = 4;
arc(pb);
ctx.restore();
t += 0.01;
if(t > 1){
t = 0;
}
requestAnimationFrame(draw);
}
function arc(...ps) {
ps.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
ctx.stroke();
});
}
function computeCurvePoint(a0,a1,a2,t){
let b = (1 -t)*(1-t) * a0 + 2 *(1-t) * t * a1 + t * t * a2;
return b;
}
- 代码首先定义了二次贝赛尔曲线三个点p0、p1、p2。其中p0、p2是起始点和终止点,p1是控制点。
- 调用arc函数在p0、p1、p2处绘制三个半径为3的圈。
- 调用moveTo和quadraticCurveTo绘制二次贝塞尔曲线。quadraticCurveTo函数在前面已经讲解过,此处不赘述。
- 之后是通过二次贝塞尔曲线方程来计算曲线上面的点pb。根据t值进行计算,计算的公式参考computeCurvePoint函数,正是二次贝塞尔曲线方程。分别调用该公式计算得出点pb的x坐标和y坐标。
let b = (1 -t)*(1-t) * a0 + 2 *(1-t) * t * a1 + t * t * a2;
- 然后调用arc函数在pb点处绘制一个圆点。
- 要起到动画效果,就要不断改变t的数值 并不断重复上面1~5步骤。所以每次t都增加0.01。然后调用requestAnimationFrame函数不断调用draw函数。
读者可以使用同样的思路 绘制点在三次贝塞尔曲线上面运动的效果。
迭代(分片)
上面的示例演示了一个点在贝塞尔曲线上面的运动。还有的时候,我们期望只绘制一曲线的一部分,而不是全部,如下图所示,那么应该如何绘制呢。

曲线动画2
其中一种比较容易想到的方式就是分片,通过前面介绍的公式,我们知道,对于给定的t值(其中: $t \in $[0,1]),可以获取曲线上面对应的点,如果t值遍历0~1之间足够多的值,那么就可以获取曲线上面足够多的点。通过贝赛尔曲线的方程计算出一系列点,然后把这一系列的点按照先后顺序连接起来,可以模拟曲线。下面的iter函数实现了迭代分片:
function iter(t,p0,p1,p2){
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
for(var i = 1;i < 100; i ++){
var tt = t/100 * i;
var pb = {};
pb.x = computeCurvePoint(p0.x,p1.x,p2.x,tt);
pb.y = computeCurvePoint(p0.y,p1.y,p2.y,tt);
ctx.lineTo(pb.x,pb.y);
}
ctx.stroke();
}
iter函数的第一个参数是t值(t值的意义:表示我们要绘制的曲线是0~t之间的一段,如果t=1,说明要绘制的是完整的贝赛尔曲线),p0、p1、p2是二次贝赛尔曲线的三个点。下面说明该函数的细节:
- 首先moveTo函数,把起始点移动到p0点
- 然后是一个for循环,迭代次数是100。
- 在循环体内,首先计算出一个新的比例值tt,在0~t之间平均计算出100个tt值。然后通过tt值计算出一个点pb,并lineTo该点。由于迭代次数是100,所以实际上会计算出100个点。
上述过程一直使用的是画直线的函数lineTo,但是只要迭代次数够大,最终用户感知的会是一条曲线,效果参考上面的图片。
使用上述办法,对于给定的任意t值,可以绘制曲线的任意部分。
如果希望绘制曲线从0到1的动画过程,只要使用上面的方法,并不断改变t值即可以。代码可以参考前面的动画示例:
t += 0.01;
if(t > 1){
t = 0;
}
requestAnimationFrame(draw);
动画绘制效果参考前面的动画图片。
任意次贝赛尔曲线
本节代码参考algorithm.html和algorithm.js
前面学习了二次、三次和任意次贝塞尔曲线的方程式。并应用方程式实现了曲线动画效果。本节将进一步解释贝塞尔曲线的图形化应用原理,并由此得出任意次贝塞尔曲线的计算方法,并改进前面的曲线迭代分片算法。
贝塞尔曲线的通用公式:

贝塞尔曲线的前世今生
canvas中贝塞尔曲线函数
在《深入理解路径(Path)》中,有提到了贝塞尔曲线的相关函数,如下所示:
- context.quadraticCurveTo(cpx, cpy, x, y)
添加指定点(x,y)到当前子路径,该点和前一个点通过指定控制点(cpx, cpy)控制的二次贝塞尔曲线连接起来。
- context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
添加指定点(x,y)到当前子路径,该点和前一个点通过指定控制点(cp1x, cp1y)和(cp2x, cp2y)控制的三次贝塞尔曲线连接起来。
下面的示例绘制一条二次贝赛尔曲线:
ctx.save();
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.quadraticCurveTo(200, 100, 200, 200);
ctx.stroke();
arc(100,100);
arc(200, 100);
arc(200, 200);
ctx.restore();
首先画笔移动到点(100,100), 然后调用二次贝赛尔曲线路径方法:
ctx.quadraticCurveTo(200, 100, 200, 200);
控制点为(200,100),终点为(200,200)。
后面的arc方法是绘制一个小圆形,示例中在曲线的端点和控制点处绘制了小圆,用于示意端点和控制点的位置。
绘制的效果如下:

二次贝塞尔曲线
下面的示例绘制一条三次贝赛尔曲线:
ctx.save();
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.bezierCurveTo(200, 100, 200, 200,300,200);
ctx.stroke();
arc(100, 100);
arc(200, 100);
arc(200, 200);
arc(300, 200);
ctx.restore();
首先画笔移动到点(100,100), 然后调用三次贝赛尔曲线路径方法:
ctx.bezierCurveTo(200, 100, 200, 200,300,200);
控制点为(200,100)和(200,200),终点为(300,200)。
后面的arc方法是绘制一个小圆形,示例中在曲线的端点和控制点处绘制了小圆,用于示意端点和控制点的位置。
绘制的效果如下:

三次贝塞尔曲线
二次贝赛尔曲线
从前面的示例中,可以看出二次贝赛尔曲线包含一个控制点和两个端点。
提示:二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来指定开始点。
下图标注出了二次贝塞尔曲线的端点和控制点:

二次贝塞尔曲线-标注点
三次贝赛尔曲线
从前面的示例中,可以看出二次贝赛尔曲线包含两个控制点和两个端点。
提示:三次贝塞尔曲线需要三个点。前两个点是用于三次贝塞尔计算中的控制点,第三个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来定义开始点。
下图标注出了三次次贝塞尔曲线的端点和控制点:

三次贝塞尔曲线-标注点
曲线动画
本节源代码参考:animate.html和animate.js与iter.html和iter.js
动画需要计算曲线的任意中间点位,对于直线而言,任意点位都是比较容易计算的,用插值算法很方便就能计算出来。而曲线就不是那么容易了,要计算曲线的中间点位,需要首先理解曲线的原理。此处首先说明贝赛尔曲线的数学方程。
贝塞尔曲线的方程
贝塞尔曲线(Bezier curve)是计算机图形学中相当重要的参数曲线,贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所发表,他第一个研究了这种矢量绘制曲线的方法,并运用贝塞尔曲线来为汽车的主体进行设计。
贝塞尔曲线(Bezier curve)通过一个方程来描述一条曲线,根据方程的最高阶数,又分为线性贝赛尔曲线,二次贝塞尔曲线、三次贝塞尔曲线和更高阶的贝塞尔曲线。
贝塞尔曲线的定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。
一次贝塞尔曲线方程
一次贝塞尔曲线,其实就是线段。假设其起始点P0,终止点为P1,线段没有控制点。一次贝塞尔曲线的表达方程:
B(t) = (1 - t) * P0 + t * P1
其中: $t \in $[0,1]
意义:由 P0 至 P1 的连续点, 描述的一条线段。该方程描述了t从0变化到1,线段上的所有点的集合。下图形象表达了上述方程:

线段
二次贝塞尔曲线方程
二次贝塞尔曲线包括了起始点P0,终止点P2,和一个控制点P1。二次贝塞尔曲线的表达方程如下:
B(t) = (1-t)2 * P0 + 2t(1-t) * P1 + t2 * P2
其中: $t \in $[0,1]
三次贝塞尔曲线方程
三次贝塞尔曲线包括了其实点P0,终止点P3,和两个控制点P1和P2。三次贝塞尔曲线的表达方程如下:
B(t) = (1 - t)3 * P0 + 3t(1-t)2 * P1 + 3t2(1-t) * P2 + t3 * P3
其中: $t \in $[0,1]
更高阶贝塞尔曲线
一般来说,最常用的是二次和三次贝塞尔曲线。但是也可能会用到更高阶的贝塞尔曲线, 此处列出贝塞尔曲线的通用公式:

动画示例
本示例会演示如下动画: 一个圆点在一条二次曲线上面从起点移动到终止点。效果如下:

曲线动画
代码如下:
function draw() {
let p0 = {x:100,y:100},
p1 = {x:200,y:100},
p2 = {x:200,y:200};
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.strokeStyle = 'red';
arc(p0,p1,p2);
ctx.restore();
ctx.save();
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y);
ctx.stroke();
ctx.restore();
let pb = {};
pb.x = computeCurvePoint(p0.x,p1.x,p2.x,t);
pb.y = computeCurvePoint(p0.y,p1.y,p2.y,t);
ctx.save();
ctx.strokeStyle = 'green';
ctx.lineWidth = 4;
arc(pb);
ctx.restore();
t += 0.01;
if(t > 1){
t = 0;
}
requestAnimationFrame(draw);
}
function arc(...ps) {
ps.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
ctx.stroke();
});
}
function computeCurvePoint(a0,a1,a2,t){
let b = (1 -t)*(1-t) * a0 + 2 *(1-t) * t * a1 + t * t * a2;
return b;
}
- 代码首先定义了二次贝赛尔曲线三个点p0、p1、p2。其中p0、p2是起始点和终止点,p1是控制点。
- 调用arc函数在p0、p1、p2处绘制三个半径为3的圈。
- 调用moveTo和quadraticCurveTo绘制二次贝塞尔曲线。quadraticCurveTo函数在前面已经讲解过,此处不赘述。
- 之后是通过二次贝塞尔曲线方程来计算曲线上面的点pb。根据t值进行计算,计算的公式参考computeCurvePoint函数,正是二次贝塞尔曲线方程。分别调用该公式计算得出点pb的x坐标和y坐标。
let b = (1 -t)*(1-t) * a0 + 2 *(1-t) * t * a1 + t * t * a2;
- 然后调用arc函数在pb点处绘制一个圆点。
- 要起到动画效果,就要不断改变t的数值 并不断重复上面1~5步骤。所以每次t都增加0.01。然后调用requestAnimationFrame函数不断调用draw函数。
读者可以使用同样的思路 绘制点在三次贝塞尔曲线上面运动的效果。
迭代(分片)
上面的示例演示了一个点在贝塞尔曲线上面的运动。还有的时候,我们期望只绘制一曲线的一部分,而不是全部,如下图所示,那么应该如何绘制呢。

曲线动画2
其中一种比较容易想到的方式就是分片,通过前面介绍的公式,我们知道,对于给定的t值(其中: $t \in $[0,1]),可以获取曲线上面对应的点,如果t值遍历0~1之间足够多的值,那么就可以获取曲线上面足够多的点。通过贝赛尔曲线的方程计算出一系列点,然后把这一系列的点按照先后顺序连接起来,可以模拟曲线。下面的iter函数实现了迭代分片:
function iter(t,p0,p1,p2){
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
for(var i = 1;i < 100; i ++){
var tt = t/100 * i;
var pb = {};
pb.x = computeCurvePoint(p0.x,p1.x,p2.x,tt);
pb.y = computeCurvePoint(p0.y,p1.y,p2.y,tt);
ctx.lineTo(pb.x,pb.y);
}
ctx.stroke();
}
iter函数的第一个参数是t值(t值的意义:表示我们要绘制的曲线是0~t之间的一段,如果t=1,说明要绘制的是完整的贝赛尔曲线),p0、p1、p2是二次贝赛尔曲线的三个点。下面说明该函数的细节:
- 首先moveTo函数,把起始点移动到p0点
- 然后是一个for循环,迭代次数是100。
- 在循环体内,首先计算出一个新的比例值tt,在0~t之间平均计算出100个tt值。然后通过tt值计算出一个点pb,并lineTo该点。由于迭代次数是100,所以实际上会计算出100个点。
上述过程一直使用的是画直线的函数lineTo,但是只要迭代次数够大,最终用户感知的会是一条曲线,效果参考上面的图片。
使用上述办法,对于给定的任意t值,可以绘制曲线的任意部分。
如果希望绘制曲线从0到1的动画过程,只要使用上面的方法,并不断改变t值即可以。代码可以参考前面的动画示例:
t += 0.01;
if(t > 1){
t = 0;
}
requestAnimationFrame(draw);
动画绘制效果参考前面的动画图片。
任意次贝赛尔曲线
本节代码参考algorithm.html和algorithm.js
前面学习了二次、三次和任意次贝塞尔曲线的方程式。并应用方程式实现了曲线动画效果。本节将进一步解释贝塞尔曲线的图形化应用原理,并由此得出任意次贝塞尔曲线的计算方法,并改进前面的曲线迭代分片算法。
贝塞尔曲线的通用公式:

贝塞尔曲线的前世今生
canvas中贝塞尔曲线函数
在《深入理解路径(Path)》中,有提到了贝塞尔曲线的相关函数,如下所示:
- context.quadraticCurveTo(cpx, cpy, x, y)
添加指定点(x,y)到当前子路径,该点和前一个点通过指定控制点(cpx, cpy)控制的二次贝塞尔曲线连接起来。
- context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
添加指定点(x,y)到当前子路径,该点和前一个点通过指定控制点(cp1x, cp1y)和(cp2x, cp2y)控制的三次贝塞尔曲线连接起来。
下面的示例绘制一条二次贝赛尔曲线:
ctx.save();
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.quadraticCurveTo(200, 100, 200, 200);
ctx.stroke();
arc(100,100);
arc(200, 100);
arc(200, 200);
ctx.restore();
首先画笔移动到点(100,100), 然后调用二次贝赛尔曲线路径方法:
ctx.quadraticCurveTo(200, 100, 200, 200);
控制点为(200,100),终点为(200,200)。
后面的arc方法是绘制一个小圆形,示例中在曲线的端点和控制点处绘制了小圆,用于示意端点和控制点的位置。
绘制的效果如下:

二次贝塞尔曲线
下面的示例绘制一条三次贝赛尔曲线:
ctx.save();
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.bezierCurveTo(200, 100, 200, 200,300,200);
ctx.stroke();
arc(100, 100);
arc(200, 100);
arc(200, 200);
arc(300, 200);
ctx.restore();
首先画笔移动到点(100,100), 然后调用三次贝赛尔曲线路径方法:
ctx.bezierCurveTo(200, 100, 200, 200,300,200);
控制点为(200,100)和(200,200),终点为(300,200)。
后面的arc方法是绘制一个小圆形,示例中在曲线的端点和控制点处绘制了小圆,用于示意端点和控制点的位置。
绘制的效果如下:

三次贝塞尔曲线
二次贝赛尔曲线
从前面的示例中,可以看出二次贝赛尔曲线包含一个控制点和两个端点。
提示:二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来指定开始点。
下图标注出了二次贝塞尔曲线的端点和控制点:

二次贝塞尔曲线-标注点
三次贝赛尔曲线
从前面的示例中,可以看出二次贝赛尔曲线包含两个控制点和两个端点。
提示:三次贝塞尔曲线需要三个点。前两个点是用于三次贝塞尔计算中的控制点,第三个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来定义开始点。
下图标注出了三次次贝塞尔曲线的端点和控制点:

三次贝塞尔曲线-标注点
曲线动画
本节源代码参考:animate.html和animate.js与iter.html和iter.js
动画需要计算曲线的任意中间点位,对于直线而言,任意点位都是比较容易计算的,用插值算法很方便就能计算出来。而曲线就不是那么容易了,要计算曲线的中间点位,需要首先理解曲线的原理。此处首先说明贝赛尔曲线的数学方程。
贝塞尔曲线的方程
贝塞尔曲线(Bezier curve)是计算机图形学中相当重要的参数曲线,贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所发表,他第一个研究了这种矢量绘制曲线的方法,并运用贝塞尔曲线来为汽车的主体进行设计。
贝塞尔曲线(Bezier curve)通过一个方程来描述一条曲线,根据方程的最高阶数,又分为线性贝赛尔曲线,二次贝塞尔曲线、三次贝塞尔曲线和更高阶的贝塞尔曲线。
贝塞尔曲线的定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。
一次贝塞尔曲线方程
一次贝塞尔曲线,其实就是线段。假设其起始点P0,终止点为P1,线段没有控制点。一次贝塞尔曲线的表达方程:
B(t) = (1 - t) * P0 + t * P1
其中: $t \in $[0,1]
意义:由 P0 至 P1 的连续点, 描述的一条线段。该方程描述了t从0变化到1,线段上的所有点的集合。下图形象表达了上述方程:

线段
二次贝塞尔曲线方程
二次贝塞尔曲线包括了起始点P0,终止点P2,和一个控制点P1。二次贝塞尔曲线的表达方程如下:
B(t) = (1-t)2 * P0 + 2t(1-t) * P1 + t2 * P2
其中: $t \in $[0,1]
三次贝塞尔曲线方程
三次贝塞尔曲线包括了其实点P0,终止点P3,和两个控制点P1和P2。三次贝塞尔曲线的表达方程如下:
B(t) = (1 - t)3 * P0 + 3t(1-t)2 * P1 + 3t2(1-t) * P2 + t3 * P3
其中: $t \in $[0,1]
更高阶贝塞尔曲线
一般来说,最常用的是二次和三次贝塞尔曲线。但是也可能会用到更高阶的贝塞尔曲线, 此处列出贝塞尔曲线的通用公式:

动画示例
本示例会演示如下动画: 一个圆点在一条二次曲线上面从起点移动到终止点。效果如下:

曲线动画
代码如下:
function draw() {
let p0 = {x:100,y:100},
p1 = {x:200,y:100},
p2 = {x:200,y:200};
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.strokeStyle = 'red';
arc(p0,p1,p2);
ctx.restore();
ctx.save();
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y);
ctx.stroke();
ctx.restore();
let pb = {};
pb.x = computeCurvePoint(p0.x,p1.x,p2.x,t);
pb.y = computeCurvePoint(p0.y,p1.y,p2.y,t);
ctx.save();
ctx.strokeStyle = 'green';
ctx.lineWidth = 4;
arc(pb);
ctx.restore();
t += 0.01;
if(t > 1){
t = 0;
}
requestAnimationFrame(draw);
}
function arc(...ps) {
ps.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
ctx.stroke();
});
}
function computeCurvePoint(a0,a1,a2,t){
let b = (1 -t)*(1-t) * a0 + 2 *(1-t) * t * a1 + t * t * a2;
return b;
}
- 代码首先定义了二次贝赛尔曲线三个点p0、p1、p2。其中p0、p2是起始点和终止点,p1是控制点。
- 调用arc函数在p0、p1、p2处绘制三个半径为3的圈。
- 调用moveTo和quadraticCurveTo绘制二次贝塞尔曲线。quadraticCurveTo函数在前面已经讲解过,此处不赘述。
- 之后是通过二次贝塞尔曲线方程来计算曲线上面的点pb。根据t值进行计算,计算的公式参考computeCurvePoint函数,正是二次贝塞尔曲线方程。分别调用该公式计算得出点pb的x坐标和y坐标。
let b = (1 -t)*(1-t) * a0 + 2 *(1-t) * t * a1 + t * t * a2;
- 然后调用arc函数在pb点处绘制一个圆点。
- 要起到动画效果,就要不断改变t的数值 并不断重复上面1~5步骤。所以每次t都增加0.01。然后调用requestAnimationFrame函数不断调用draw函数。
读者可以使用同样的思路 绘制点在三次贝塞尔曲线上面运动的效果。
迭代(分片)
上面的示例演示了一个点在贝塞尔曲线上面的运动。还有的时候,我们期望只绘制一曲线的一部分,而不是全部,如下图所示,那么应该如何绘制呢。

曲线动画2
其中一种比较容易想到的方式就是分片,通过前面介绍的公式,我们知道,对于给定的t值(其中: $t \in $[0,1]),可以获取曲线上面对应的点,如果t值遍历0~1之间足够多的值,那么就可以获取曲线上面足够多的点。通过贝赛尔曲线的方程计算出一系列点,然后把这一系列的点按照先后顺序连接起来,可以模拟曲线。下面的iter函数实现了迭代分片:
function iter(t,p0,p1,p2){
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
for(var i = 1;i < 100; i ++){
var tt = t/100 * i;
var pb = {};
pb.x = computeCurvePoint(p0.x,p1.x,p2.x,tt);
pb.y = computeCurvePoint(p0.y,p1.y,p2.y,tt);
ctx.lineTo(pb.x,pb.y);
}
ctx.stroke();
}
iter函数的第一个参数是t值(t值的意义:表示我们要绘制的曲线是0~t之间的一段,如果t=1,说明要绘制的是完整的贝赛尔曲线),p0、p1、p2是二次贝赛尔曲线的三个点。下面说明该函数的细节:
- 首先moveTo函数,把起始点移动到p0点
- 然后是一个for循环,迭代次数是100。
- 在循环体内,首先计算出一个新的比例值tt,在0~t之间平均计算出100个tt值。然后通过tt值计算出一个点pb,并lineTo该点。由于迭代次数是100,所以实际上会计算出100个点。
上述过程一直使用的是画直线的函数lineTo,但是只要迭代次数够大,最终用户感知的会是一条曲线,效果参考上面的图片。
使用上述办法,对于给定的任意t值,可以绘制曲线的任意部分。
如果希望绘制曲线从0到1的动画过程,只要使用上面的方法,并不断改变t值即可以。代码可以参考前面的动画示例:
t += 0.01;
if(t > 1){
t = 0;
}
requestAnimationFrame(draw);
动画绘制效果参考前面的动画图片。
任意次贝赛尔曲线
本节代码参考algorithm.html和algorithm.js
前面学习了二次、三次和任意次贝塞尔曲线的方程式。并应用方程式实现了曲线动画效果。本节将进一步解释贝塞尔曲线的图形化应用原理,并由此得出任意次贝塞尔曲线的计算方法,并改进前面的曲线迭代分片算法。
贝塞尔曲线的通用公式:

贝塞尔曲线的前世今生