canvas提供的方法
1.绘制二次贝塞尔曲线:quadraticCurveTo(cp1x, cp1y, x, y)
ctx.moveTo(10, 200); //起始点
var cp1x = 40, cp1y = 100; //控制点
var x = 200, y = 200; // 结束点
//绘制二次贝塞尔曲线
ctx.quadraticCurveTo(cp1x, cp1y, x, y);
2.绘制三次贝塞尔曲线:bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
ctx.moveTo(40, 200); //起始点
var cp1x = 20, cp1y = 100; //控制点1
var cp2x = 100, cp2y = 120; //控制点2
var x = 200, y = 200; // 结束点
//绘制二次贝塞尔曲线
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
贝塞尔曲线的动画演示及公式
1.一次贝塞尔曲线:B(t) = P0(1-t) + tP1,t∈[0,1]
2.二次bezier公式:B(t) = P0(1-t)² + 2P1 t(1-t) +P2t²,t∈[0,1]
3.三次bezier公式:B(t) = P0(1-t)³ + 3P1t(1-t)² + 3p2t²(1-t)+P3t³,t∈[0,1]
4.根据以上得出贝塞尔曲线通用公式,详见贝塞尔公式推导
利用公式实现canvas轨迹运动
根据前面的公式,我们就能得求出贝塞尔曲线上的点满足的公式,以下就以二次贝塞尔曲线为例说明。
- 二次贝塞尔曲线的公式是:B(t) = P0(1-t)² + 2P1 t(1-t) +P2t²,t∈[0,1]
- 所以弧线上的点满足的方程:
x = Math.pow(1-t, 2) * x1 + 2 * t * (1-t) * cx + Math.pow(t, 2) * x2
y = Math.pow(1-t, 2) * y1 + 2 * t * (1-t) * cy + Math.pow(t, 2) * y2
- 利用公式写一个demo,实现小球沿着贝塞尔曲线运动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body,
canvas {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<canvas id="canvas" style="background-color: pink;"></canvas>
</body>
<script>
const canvas = document.getElementById('canvas')
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
//起点
var [x1, y1] = [10, 10];
//小球起点
var [x, y] = [10, 10];
//控制点
var [cx, cy] = [100, 0];
//终点
var [x2, y2] = [200, 200]
var t = 0
//画一条线
function draw() {
ctx.beginPath()
ctx.moveTo(10, 10)
ctx.quadraticCurveTo(cx, cy, x2, y2)
ctx.stroke()
ctx.closePath()
}
/*贝塞尔曲线上点位的控制*/
function computedPosition() {
if (y > 200) {
t = 0
x = 0
y = 0
return
}
x = Math.pow(1 - t, 2) * x1 + 2 * t * (1 - t) * cx + Math.pow(t, 2) * x2
y = Math.pow(1 - t, 2) * y1 + 2 * t * (1 - t) * cy + Math.pow(t, 2) * y2
}
function drawPoint() {
computedPosition()
ctx.beginPath()
ctx.fillStyle = "red"
ctx.arc(x, y, 10, 0, 2 * Math.PI)
ctx.fill()
}
let loopDraw = () => {
requestAnimationFrame(loopDraw)
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
draw()
drawPoint()
t += 0.007//控制动画移动速度
}
loopDraw()//启动动画
</script>
</html>
- 那么三次的实现只需要修改部分代码
//……
//控制点
var [cx1, cy1] = [100, 0];
var [cx2, cy2] = [100, 250]
//……
ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2)
//……
/*贝塞尔曲线上点位的控制*/
function computedPosition() {
//...
x = x1 * Math.pow((1 - t), 3) + 3 * cx1 * t * Math.pow((1 - t), 2) + 3 * cx2 * Math.pow(t, 2) * (1 - t) + x2 * Math.pow(t, 3)
y = y1 * Math.pow((1 - t), 3) + 3 * cy1 * t * Math.pow((1 - t), 2) + 3 * cy2 * Math.pow(t, 2) * (1 - t) + y2 * Math.pow(t, 3)
}
如何实现绘制n次贝塞尔曲线呢?
通过上面的两个的两个例子我们可以增强对canvas动画实现的理解,即一切变化都基于t(时间)、x,y,z(位置)实现的。以上两个例子只是做个引子,二次贝塞尔曲线多用于加入购物车实现,三次我还没想到具体的应用场景。以上的两个例子只是个引子,我们的最终的目的是要实现轨迹运动,可以随意写入控制点,以上实现二次和三次使用了不同的方式。那实现不同次方的贝塞尔曲线都需要单独写一个方法吗?别急,先来继续搞清楚贝塞尔曲线的公式。
公式理解
贝塞尔曲线通用公式:
可以从具体的例子中找到抽象规律:
- N = 3: P = (1-t)^2P0 + 2(1-t)tP1 + t^2*P2
- N = 4: P = (1-t)^3P0 + 3(1-t)^2tP1 + 3(1-t)t^2P2 + t^3*P3
- N = 5: P = (1-t)^4P0 + 4(1-t)^3tP1 + 6(1-t)2*t2P2 + 4(1-t)t^3P3 + t^4*P4
将贝塞尔曲线一般参数公式中的表达式用如下方式表示:设有常数 a,b 和 c,则该表达式可统一表示为如下形式
a * (1 – t)^b * t^c * Pn
分析当 N 分别为3,4,5 时对应 a,b,c 的值:如 N = 3 时,公式有三个表达式,第一个表达式为 (1-t)^2*P0,其对应 a,b,c 值分别为:1,2,0
N = 3: 1,2,0 2,1,1 1,0,2
a: 1 2 1
b: 2 1 0
c: 0 1 2
N = 4: 1,3,0 3,2,1 3,1,2 1,0,3
a: 1 3 3 1
b: 3 2 1 0
c: 0 1 2 3
N = 5: 1,4,0 4,3,1 6,2,2 4,1,3 1,0,4
a: 1 4 6 4 1
b: 4 3 2 1 0
c: 0 1 2 3 4
根据上面的分析就可以总结出 a,b,c 对应的取值规则:
b: (N – 1) 递减到 0 (b 为 1-t 的幂)
c: 0 递增到 (N – 1) (c 为 t 的幂)
a: 在 N 分别为 1,2,3,4,5 时将其值用如下形式表示:
N=1:———1
N=2:——–1 1
N=3:——1 2 1
N=4:—–1 3 3 1
N=5:—1 4 6 4 1
使用组合实现
//组合
function C(n, i) {
return f(n) / f(i) / f(n - i)
}
//阶乘公式 n!
//阶乘 factorial
function f(n) {
if (n < 0) {
return -1
} else if (n === 0 || n === 1) {
return 1
} else {
return (n * f(n - 1))
}
}