Avatar
返回文章列表
技术

Canvas 2D 粒子效果实现笔记

2025.04.15 5 min
Canvas 2D 粒子效果实现笔记

Canvas 2D 粒子效果实现笔记

在构建这个博客的首页背景时,我选择使用 Canvas 2D 而非 Three.js。原因有三:

  1. 体积更小 — 无需加载 500KB+ 的 3D 引擎
  2. SSR 友好 — Canvas 2D 在服务端渲染时不会出问题
  3. 维护简单 — 代码量更少,更容易理解

核心原理

interface Particle {
 x: number;
 y: number;
 vx: number;
 vy: number;
}

function updateParticles(particles: Particle[], mouse: { x: number; y: number }) {
 for (const p of particles) {
  // 基础移动
  p.x += p.vx;
  p.y += p.vy;

  // 鼠标排斥
  const dx = p.x - mouse.x;
  const dy = p.y - mouse.y;
  const dist = Math.sqrt(dx * dx + dy * dy);
  if (dist < 100 && dist > 0) {
   const force = (100 - dist) * 0.001;
   p.vx += (dx / dist) * force;
   p.vy += (dy / dist) * force;
  }

  // 边界反弹
  if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
  if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
 }
}

连线绘制

只有当粒子间距小于阈值时才绘制连线,连线透明度与距离成反比:

const CONNECTION_DISTANCE = 120;

for (let i = 0; i < particles.length; i++) {
 for (let j = i + 1; j < particles.length; j++) {
  const dx = particles[i].x - particles[j].x;
  const dy = particles[i].y - particles[j].y;
  const dist = Math.sqrt(dx * dx + dy * dy);

  if (dist < CONNECTION_DISTANCE) {
   const alpha = 1 - dist / CONNECTION_DISTANCE;
   ctx.strokeStyle = `rgba(240, 192, 64, ${alpha * 0.3})`;
   ctx.beginPath();
   ctx.moveTo(particles[i].x, particles[i].y);
   ctx.lineTo(particles[j].x, particles[j].y);
   ctx.stroke();
  }
 }
}

性能优化

  • 粒子数量控制在 100 个以内
  • 使用 requestAnimationFrame 而非 setInterval
  • 组件卸载时清理动画帧
  • 使用 devicePixelRatio 适配高分屏

实际效果非常流畅,CPU 占用也很低。