二次贝塞尔曲线需要三个点的坐标数据:曲线起始点坐标(x1,y1)、曲线控制点坐标(cpx,cpy)和曲线终点坐标(x2,y2)。在 canvas 画布中,起始点坐标默认使用上一次绘制图形的路径终点坐标,没有路径终点坐标时起始点坐标为(0,0),也可以使用 moveTo(x,y) 指令重新定义。canvas 绘制二次贝塞尔曲线有专门的指令,quadraticCurseTo(cpx, cpy, endX, endY), 其中,cpx 和 cpy 为曲线控制点坐标,endX 和 endY 为曲线终点坐标,曲线起点坐标取上一回路径终点坐标或通过 moveTo(x,y) 指令定义。假设画笔标识为 ctx,我们来绘制一条二次贝塞尔曲线:
ctx.moveTo(50, 100); ctx.quadraticCurveTo(120, 0, 250, 100);
这表示,曲线从(50,100)出发,到(250,100)停车,曲线的控制点是(120, 10),效果如下示例所示(小圆点用于标识三个点的位置):
影响二次贝塞尔曲线的外观主要是控制点,上例中的小绿点就是控制点。控制点可以在曲线上,若此,绘制出来的将不是曲线而是一条直线;控制点可以为正负数、可以超出画布的范围,不同的数值将直接影响曲线的曲率和最终外观。
单纯使用二次贝塞尔曲线,我们就可以绘制出奇妙的动态图案。以下代码,我们仅来回改变控制点(cpx,cpy)中的cpy值,作出的动态图案十分令人惊艳,它还有无限可能:
<canvas id="canv" width="300" height="200"></canvas>
<script>
let ctx = canv.getContext('2d');
let ww = canv.width, hh = canv.height;
/* 全局变量
ctrY :控制点Y坐标
step :控制点Y坐标变化幅度
total :曲线总数
raf :关键帧动画标识
*/
let ctrY = hh / 2, step = 0.25, total = 20, raf = null;
//函数 :生成随机rgb颜色
let mkRgba = (opacity=0.5) => {
let ar = [256,256,256].map((item) => Math.floor(Math.random() * item));
ar.push( opacity || (.5 + Math.random() * .5).toFixed(1));
return 'rgba(' + ar.join(',') + ')';
}
//创建一个曲线类
class curveLine {
//类构造
constructor(x1, y1, cpx, cpy, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.cpx = cpx;
this.cpy = cpy;
this.x2 = x2;
this.y2 = y2;
this.color = 'green';
this.lineWidth = 4;
};
//类的绘制行为
draw(ctx) {
ctx.save();
ctx.strokeStyle = this.color;
ctx.lineCap = 'round';
ctx.lineWidth = this.lineWidth;
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
ctx.quadraticCurveTo(this.cpx, this.cpy, this.x2, this.y2);
ctx.stroke();
ctx.closePath();
ctx.restore();
};
};
//批量绘制曲线
let draw = () => {
ctx.clearRect(0, 0, ww, hh);
let add = ww - 20;
for(let i = 0; i < total; i ++) {
let cl = new curveLine(); //创建曲线类实例
cl.x1 = ww / 2;
cl.y1 = hh - 5;
cl.cpx = (ww + add) / total * i - add / 2 + 5;
cl.cpy = ctrY;
cl.x2 = ww / 2;
cl.y2 = 5;
cl.color = mkRgba(0.7);
cl.lineWidth = 4;
cl.draw(ctx);
}
};
//渲染效果
let render = () => {
ctrY += step;
if(ctrY > hh || ctrY < 0) step = -step;
draw();
raf = requestAnimationFrame(render); //调用关键帧动画接口
};
render();
//画布单击事件 :暂停或启用动画
canv.onclick = () => {
raf = raf ? cancelAnimationFrame(raf) : requestAnimationFrame(render);
};
</script>
以上代码,可以保存为本地 .html 文档或将代码拿到 pencil code 运行以查看效果,提醒一下,动画是可以暂停和继续运行的,方法是单击画布。
|