Code

Canvas

Particle System

Advanced Physics-based particles with buoyancy, turbulence, and additive (lighter) blending for a glowing fire-fountain effect.

interface Particle {
  x: number; y: number;
  vx: number; vy: number;
  life: number;   // 1 → 0
  decay: number;
  size: number;
  hue: number;
}

const step = () => {
  // Semi-transparent dark overlay = fade trail
  ctx.fillStyle = 'rgba(10, 10, 30, 0.15)';
  ctx.fillRect(0, 0, W, H);

  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];

    // Physics
    p.vy -= 0.06;           // buoyancy (upward)
    p.vx += (Math.random() - 0.5) * 0.2; // turbulence
    p.vx *= 0.99;           // drag
    p.vy *= 0.99;
    p.x += p.vx;
    p.y += p.vy;
    p.life -= p.decay;

    if (p.life <= 0) { particles.splice(i, 1); continue; }

    // Additive blending — particles add together = glow
    ctx.globalCompositeOperation = 'lighter';
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
    ctx.fillStyle = `hsla(${p.hue}, 100%, 60%, ${p.life})`;
    ctx.fill();
    ctx.globalCompositeOperation = 'source-over';
  }

  requestAnimationFrame(step);
};

// Spawn particles
const emit = (ex: number, ey: number) => {
  const angle = -Math.PI/2 + (Math.random()-0.5) * 1.2;
  const speed = 2 + Math.random() * 3;
  particles.push({
    x: ex, y: ey,
    vx: Math.cos(angle) * speed,
    vy: Math.sin(angle) * speed,
    life: 1,
    decay: 0.008 + Math.random() * 0.006,
    size: 3 + Math.random() * 4,
    hue: baseHue + Math.random() * 60,
  });
};
Key Concepts
  • Each particle stores life (0→1) decremented by decay each frame
  • Upward buoyancy: subtract a small constant from vy every frame
  • Turbulence: add a small random value to vx each frame
  • globalCompositeOperation = 'lighter' adds RGB values — overlapping particles glow
  • Semi-transparent clear (rgba(…, 0.15)) fades the trail gradually
  • Iterate backward (for i = length-1; i >= 0; i--) when splicing to avoid index skipping