以前的文章提到過 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
這樣咱們就完成一個粒子效果啦~~~動畫
本人正在編寫數據可視化之路系列文章,輸出一些可視化方面的教程和經驗,你能夠經過如下途徑閱讀它們。