以前的文章提到过 canvas 动画的难点和通常规则。canvas 采用了一种叫作 当即渲染模式 ,调用绘制方法后 canvas 会直接将图形绘制到画布上,而后就再也不管它,canvas 也不会记住你绘制的任何图形。这和 dom 模型不一样,咱们始终能够在内存中获取到 dom 元素的引用。基于这种特色,咱们在动画每一帧以前都会清除画布,从新绘制一遍。绘制步骤以下:javascript
本文咱们作一个粒子动画效果看看:java
从图中能够看出,粒子运动效果依据如下规则:git
首先是粒子类,它接受一个渲染上下文做为参数,并封装了粒子的状态和绘图方法。github
class Particle {
ctx: CanvasRenderingContext2D;
x: number;
y: number;
vx: number;
vy: number;
constructor(ctx: CanvasRenderingContext2D) {
this.ctx = ctx;
this.x = random(0, CANVAS_WIDTH);
this.y = random(0, CANVAS_HEIGHT);
this.vx = random(-VELOCITY, VELOCITY);
this.vy = random(-VELOCITY, VELOCITY);
}
draw() {
if (this.x > CANVAS_WIDTH || this.x < 0) {
this.vx = -this.vx;
}
if (this.y > CANVAS_HEIGHT || this.y < 0) {
this.vy = -this.vy;
}
this.x += this.vx;
this.y += this.vy;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, PARTICLE_RADIUS, 0, Math.PI * 2);
// this.ctx.closePath();
this.ctx.fill();
}
}
复制代码
其次是鼠标粒子,它接受渲染上下文和dom容器做为参数,和粒子类相同,而且监听容器的鼠标位置以更新其坐标。canvas
class MouseParticle {
ctx: CanvasRenderingContext2D;
x = -1;
y = -1;
// 单例的
static instance: MouseParticle;
constructor(ctx: CanvasRenderingContext2D, container: HTMLCanvasElement) {
this.ctx = ctx;
if (MouseParticle.instance) return MouseParticle.instance;
container.addEventListener("mouseenter", e => {
this.x = e.clientX;
this.y = e.clientY;
});
container.addEventListener("mousemove", e => {
this.x = e.clientX;
this.y = e.clientY;
});
container.addEventListener("mouseleave", e => {
// 移到canvas外
this.x = -1;
this.y = -1;
});
MouseParticle.instance = this;
return this;
}
draw() {
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, PARTICLE_RADIUS, 0, Math.PI * 2);
this.ctx.fill();
}
}
复制代码
接着咱们初始化粒子和鼠标粒子对象,并保存进粒子数组。数组
const particles: (Particle | MouseParticle)[] = [];
for (let index = 0; index < PARTICLE_COUNT; index++) {
const particle = new Particle(ctx);
particles.push(particle);
}
const mouseParticle = new MouseParticle(ctx, canvas);
复制代码
接下来是咱们的主绘图函数,它会在每一帧调用,根据粒子的状态重绘画布。dom
function draw(ctx: CanvasRenderingContext2D) {
requestAnimationFrame(() => draw(ctx));
// 清除画布并重绘粒子
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
particles.forEach(particle => {
particle.draw();
});
// 重绘线
ctx.save();
particles.forEach(source => {
particles.forEach(target => {
const xDistance = Math.abs(source.x - target.x);
const yDistance = Math.abs(source.y - target.y);
const dis = Math.round(
Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2))
);
if (dis < PARTICLE_DISTANCE) {
ctx.globalAlpha = dis / PARTICLE_DISTANCE;
ctx.beginPath();
ctx.moveTo(source.x, source.y);
ctx.lineTo(target.x, target.y);
ctx.closePath();
ctx.stroke();
}
});
});
}
复制代码
在主绘图函数里咱们遍历粒子数组,当两个粒子距离小于必定距离时连线。函数
完整的代码在这里post
这样咱们就完成一个粒子效果啦~~~动画
本人正在编写数据可视化之路系列文章,输出一些可视化方面的教程和经验,你能够经过如下途径阅读它们。