从零构建支持粒子系统与物理效果的网页动画引擎
一、现代Web动画技术选型
实现高性能网页动画的三大技术路径对比:
技术方案 | 优势 | 适用场景 |
---|---|---|
CSS动画 | 简单易用、GPU加速 | 简单UI动效、过渡动画 |
SVG动画 | 矢量缩放、DOM操作 | 图标动画、数据可视化 |
Canvas动画 | 高性能、完全控制 | 复杂特效、游戏开发 |
本文将聚焦Canvas技术,实现可复用的动画引擎架构。
二、动画引擎核心架构
1. 引擎类结构设计
class AnimationEngine {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.width = canvas.width;
this.height = canvas.height;
this.particles = [];
this.forces = [];
this.running = false;
this.lastTime = 0;
this.fps = options.fps || 60;
this.frameInterval = 1000 / this.fps;
}
addParticle(particle) {
this.particles.push(particle);
}
addForce(force) {
this.forces.push(force);
}
start() {
this.running = true;
this.lastTime = performance.now();
this._animate();
}
stop() {
this.running = false;
}
_animate() {
if (!this.running) return;
const now = performance.now();
const deltaTime = now - this.lastTime;
if (deltaTime > this.frameInterval) {
this._update(deltaTime);
this._render();
this.lastTime = now - (deltaTime % this.frameInterval);
}
requestAnimationFrame(() => this._animate());
}
_update(deltaTime) {
// 物理模拟和粒子状态更新
this._applyForces();
this._updateParticles(deltaTime);
this._handleCollisions();
}
_render() {
// 清空画布
this.ctx.clearRect(0, 0, this.width, this.height);
// 渲染所有粒子
this.particles.forEach(particle => {
particle.render(this.ctx);
});
}
}
2. 粒子系统实现
class Particle {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = { x: 0, y: 0 };
this.acceleration = { x: 0, y: 0 };
this.mass = radius * radius;
this.life = 1000; // 粒子生命周期(ms)
}
update(deltaTime) {
// 更新速度
this.velocity.x += this.acceleration.x;
this.velocity.y += this.acceleration.y;
// 更新位置
this.x += this.velocity.x * deltaTime / 1000;
this.y += this.velocity.y * deltaTime / 1000;
// 重置加速度
this.acceleration = { x: 0, y: 0 };
// 更新生命周期
this.life -= deltaTime;
}
applyForce(force) {
this.acceleration.x += force.x / this.mass;
this.acceleration.y += force.y / this.mass;
}
render(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
}
三、物理效果实现
1. 力场模拟
// 重力场
class Gravity {
constructor(force = 9.8) {
this.force = force;
this.direction = { x: 0, y: 1 };
}
applyTo(particle) {
particle.applyForce({
x: this.direction.x * this.force * particle.mass,
y: this.direction.y * this.force * particle.mass
});
}
}
// 风力场
class Wind {
constructor(force, direction) {
this.force = force;
this.direction = direction || {
x: Math.random() * 2 - 1,
y: Math.random() - 0.5
};
}
applyTo(particle) {
particle.applyForce({
x: this.direction.x * this.force,
y: this.direction.y * this.force
});
}
}
2. 碰撞检测优化
class CollisionSystem {
constructor(engine) {
this.engine = engine;
this.gridSize = 50; // 网格大小
this.grid = {};
}
_hashPosition(x, y) {
return `${Math.floor(x/this.gridSize)}_${Math.floor(y/this.gridSize)}`;
}
_buildGrid() {
this.grid = {};
this.engine.particles.forEach(particle => {
const hash = this._hashPosition(particle.x, particle.y);
if (!this.grid[hash]) this.grid[hash] = [];
this.grid[hash].push(particle);
});
}
checkCollisions() {
this._buildGrid();
Object.values(this.grid).forEach(cell => {
if (cell.length < 2) return;
// 检查同一网格内的粒子碰撞
for (let i = 0; i < cell.length; i++) {
for (let j = i + 1; j < cell.length; j++) {
this._checkPair(cell[i], cell[j]);
}
}
});
}
_checkPair(p1, p2) {
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < p1.radius + p2.radius) {
this._resolveCollision(p1, p2, dx/distance, dy/distance);
}
}
}
四、特效系统开发
1. 粒子发射器
class ParticleEmitter {
constructor(x, y) {
this.x = x;
this.y = y;
this.particlesPerSecond = 30;
this.lastEmitTime = 0;
this.particleConfig = {
radius: () => Math.random() * 3 + 1,
color: () => `hsl(${Math.random()*60+10}, 100%, 50%)`,
velocity: () => ({
x: (Math.random() - 0.5) * 100,
y: (Math.random() - 0.5) * 100
})
};
}
update(engine, time) {
const shouldEmit = time - this.lastEmitTime > 1000/this.particlesPerSecond;
if (!shouldEmit) return;
this.lastEmitTime = time;
const particle = new Particle(
this.x,
this.y,
this.particleConfig.radius(),
this.particleConfig.color()
);
const velocity = this.particleConfig.velocity();
particle.velocity = velocity;
engine.addParticle(particle);
}
}
2. 高级渲染效果
// 粒子轨迹效果
Particle.prototype.render = function(ctx) {
// 创建径向渐变
const gradient = ctx.createRadialGradient(
this.x, this.y, 0,
this.x, this.y, this.radius
);
gradient.addColorStop(0, this.color);
gradient.addColorStop(1, 'transparent');
// 绘制发光效果
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius * 1.5, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.globalCompositeOperation = 'lighter';
ctx.fill();
ctx.closePath();
// 恢复混合模式
ctx.globalCompositeOperation = 'source-over';
};
// 图像粒子效果
class ImageParticle extends Particle {
constructor(x, y, image) {
super(x, y, image.width/2, null);
this.image = image;
this.rotation = 0;
this.alpha = 1;
}
render(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.globalAlpha = this.alpha;
ctx.drawImage(
this.image,
-this.radius,
-this.radius,
this.radius * 2,
this.radius * 2
);
ctx.restore();
}
}
五、性能优化实战
1. 离屏Canvas技术
class ParticleCache {
constructor(particle) {
this.offscreenCanvas = document.createElement('canvas');
this.offscreenCanvas.width = particle.radius * 4;
this.offscreenCanvas.height = particle.radius * 4;
const ctx = this.offscreenCanvas.getContext('2d');
particle.render(ctx);
}
render(ctx, x, y) {
ctx.drawImage(
this.offscreenCanvas,
x - this.offscreenCanvas.width/2,
y - this.offscreenCanvas.height/2
);
}
}
// 使用缓存渲染
const cache = new ParticleCache(new Particle(0, 0, 10, 'red'));
particles.forEach(particle => {
cache.render(ctx, particle.x, particle.y);
});
2. Web Workers并行计算
// worker.js
self.onmessage = function(e) {
const { particles, forces, deltaTime } = e.data;
// 在Worker中计算物理模拟
const updatedParticles = particles.map(particle => {
const p = {...particle};
forces.forEach(force => {
if (force.type === 'gravity') {
p.velocity.y += 9.8 * deltaTime / 1000;
}
// 其他力场计算...
});
p.x += p.velocity.x * deltaTime / 1000;
p.y += p.velocity.y * deltaTime / 1000;
return p;
});
self.postMessage(updatedParticles);
};
// 主线程
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
this.particles = e.data;
};
// 每帧发送数据到Worker
worker.postMessage({
particles: this.particles,
forces: this.forces,
deltaTime: deltaTime
});