Code

Canvas

Flow Field

Advanced 1,200 particles following a dynamic vector field derived from layered sine/cosine noise — a Perlin noise substitute.

// Smooth noise via layered trigonometry
const noise = (x: number, y: number, t: number) =>
  Math.sin(x * 0.006 + t * 0.8) *
  Math.cos(y * 0.007 + t * 0.5) +
  Math.sin(x * 0.013 - t * 0.4) *
  Math.cos(y * 0.011 + t * 0.3) * 0.5 +
  Math.sin(x * 0.025 + y * 0.015 + t * 1.1) * 0.25;

interface Agent { x: number; y: number; hue: number; }

const agents: Agent[] = Array.from({ length: 1200 },
  () => ({ x: Math.random()*W, y: Math.random()*H,
           hue: Math.random()*360 }));

let t = 0;

const step = () => {
  // Very slow fade — trails persist for many frames
  ctx.fillStyle = 'rgba(10, 10, 30, 0.04)';
  ctx.fillRect(0, 0, W, H);

  for (const a of agents) {
    // Sample noise → direction angle
    const angle = noise(a.x, a.y, t) * Math.PI * 2.5;
    const speed = 1.5;

    a.x += Math.cos(angle) * speed;
    a.y += Math.sin(angle) * speed;

    // Toroidal wrap
    if (a.x < 0) a.x = W;  if (a.x > W) a.x = 0;
    if (a.y < 0) a.y = H;  if (a.y > H) a.y = 0;

    a.hue = (a.hue + 0.3) % 360;

    ctx.beginPath();
    ctx.arc(a.x, a.y, 1, 0, Math.PI * 2);
    ctx.fillStyle = `hsla(${a.hue}, 90%, 65%, 0.7)`;
    ctx.fill();
  }
  t += 0.004;
  requestAnimationFrame(step);
};
Key Concepts
  • Each particle reads the noise value at its position to get a steering angle
  • Layered sine/cosine at different frequencies approximates Perlin noise cheaply
  • The t offset evolves the field over time — field appears to flow
  • Extremely slow fade (alpha 0.04) produces long, dense trailing streaks
  • HSL colour with incrementing hue gives each particle a rainbow trail
  • For production: use a precomputed Perlin/Simplex noise grid sampled by position