Canvas粒子系统终极指南:从基础运动到复杂交互的全流程实现

发布于:2025-03-30 ⋅ 阅读:(32) ⋅ 点赞:(0)


一、粒子系统基础架构

1.1 粒子数据结构设计

class Particle {
  constructor(x, y) {
    this.pos = { x, y };
    this.vel = { x: Math.random()*4 - 2, y: Math.random()*4 - 2 };
    this.acc = { x: 0, y: 0.1 };
    this.size = Math.random()*8 + 2;
    this.life = 200;
  }

  update() {
    this.vel.x += this.acc.x;
    this.vel.y += this.acc.y;
    this.pos.x += this.vel.x;
    this.pos.y += this.vel.y;
    this.life--;
  }
}

1.2 粒子系统管理器

class ParticleSystem {
  constructor() {
    this.particles = [];
    this.emitRate = 5;
    this.gravity = 0.1;
  }

  emit(x, y) {
    for(let i=0; i<this.emitRate; i++) {
      this.particles.push(new Particle(x, y));
    }
  }

  update() {
    this.particles = this.particles.filter(p => p.life > 0);
    this.particles.forEach(p => p.update());
  }
}

二、基础粒子效果实现

2.1 重力场模拟

// 初始化时设置重力
class GravityParticle extends Particle {
  constructor(x, y) {
    super(x, y);
    this.acc.y = 0.3; // 增强重力效果
  }
}

// 鼠标点击触发粒子发射
canvas.addEventListener('click', (e) => {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  particleSystem.emit(x, y);
});

2.2 弹性碰撞效果

class BounceParticle extends Particle {
  update() {
    super.update();
    
    // 水平边界碰撞
    if(this.pos.x < 0 || this.pos.x > canvas.width) {
      this.vel.x *= -0.8;
      this.pos.x = Math.max(0, Math.min(this.pos.x, canvas.width));
    }
    
    // 垂直边界碰撞
    if(this.pos.y < 0 || this.pos.y > canvas.height) {
      this.vel.y *= -0.8;
      this.pos.y = Math.max(0, Math.min(this.pos.y, canvas.height));
    }
  }
}

三、高级交互实现

3.1 鼠标吸引效果

class AttractorParticle extends Particle {
  constructor(x, y) {
    super(x, y);
    this.attraction = 0.02;
  }

  update(mousePos) {
    super.update();
    
    const dx = mousePos.x - this.pos.x;
    const dy = mousePos.y - this.pos.y;
    const distance = Math.sqrt(dx*dx + dy*dy);
    
    if(distance < 200) {
      const force = (200 - distance) * this.attraction;
      this.vel.x += (dx/distance) * force;
      this.vel.y += (dy/distance) * force;
    }
  }
}

3.2 颜色渐变粒子

class GradientParticle extends Particle {
  constructor(x, y) {
    super(x, y);
    this.colorStart = `hsl(${Math.random()*360}, 80%, 60%)`;
    this.colorEnd = `hsl(${Math.random()*360}, 80%, 30%)`;
  }

  draw(ctx) {
    const gradient = ctx.createRadialGradient(
      this.pos.x, this.pos.y, 
      0, this.pos.x, this.pos.y, this.size
    );
    gradient.addColorStop(0, this.colorStart);
    gradient.addColorStop(1, this.colorEnd);
    
    ctx.beginPath();
    ctx.arc(this.pos.x, this.pos.y, this.size, 0, Math.PI*2);
    ctx.fillStyle = gradient;
    ctx.fill();
  }
}

四、性能优化策略

4.1 粒子池复用

class PooledParticleSystem extends ParticleSystem {
  constructor() {
    super();
    this.pool = [];
  }

  emit(x, y) {
    for(let i=0; i<this.emitRate; i++) {
      const particle = this.pool.length ? this.pool.pop() : new Particle(x, y);
      particle.reset(x, y);
      this.particles.push(particle);
    }
  }

  remove(particle) {
    this.pool.push(particle);
  }
}

4.2 分层渲染

function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 背景层(透明粒子)
  backgroundParticles.forEach(p => p.draw(ctx));
  
  // 交互层(高亮粒子)
  interactiveParticles.forEach(p => p.draw(ctx));
  
  requestAnimationFrame(render);
}

五、复杂效果实现

5.1 烟花爆炸效果

class FireworkParticle extends Particle {
  constructor(x, y) {
    super(x, y);
    this.vel.y = -Math.random()*6 - 8;
    this.acc.y = 0.05;
    this.size = Math.random()*15 + 5;
  }

  draw(ctx) {
    ctx.beginPath();
    ctx.arc(this.pos.x, this.pos.y, this.size, 0, Math.PI*2);
    ctx.fillStyle = `hsl(${Math.random()*360}, 80%, 60%)`;
    ctx.fill();
  }
}

5.2 流体模拟

class FluidParticle extends Particle {
  constructor(x, y) {
    super(x, y);
    this.density = Math.random()*50 + 10;
  }

  update() {
    super.update();
    this.vel.x *= 0.95;
    this.vel.y *= 0.95;
  }

  draw(ctx) {
    ctx.beginPath();
    ctx.arc(this.pos.x, this.pos.y, this.size, 0, Math.PI*2);
    ctx.fillStyle = `rgba(0, 255, 255, ${this.density/100})`;
    ctx.fill();
  }
}

到这里,这篇文章就和大家说再见啦!我的主页里还藏着很多 篇 Vue 实战干货,感兴趣的话可以点击头像看看,说不定能找到你需要的解决方案~
创作这篇内容花了很多的功夫。如果它帮你解决了问题,或者带来了启发,欢迎:
点个赞❤️ 让更多人看到优质内容
关注「前端极客探险家」🚀 每周解锁新技巧
收藏文章⭐️ 方便随时查阅
📢 特别提醒:
转载请注明原文链接,商业合作请私信联系
感谢你的阅读!我们下篇文章再见~ 💕

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到