Lab / 40 min build

Particles that spell a word

A few thousand particles find their place in a word and scatter when your finger gets close.

CanvasParticles

Open full screen ↗

The core

// Draw the text off-screen and keep one target point every `gap` pixels.
// Line breaks in `text` stack the words, which suits a 9:16 frame.
export function textTargets(text, w, h, gap) {
  const c = document.createElement('canvas');
  c.width = w;
  c.height = h;
  const g = c.getContext('2d', { willReadFrequently: true });
  const lines = text.split('\n');
  const font = (s) => `800 ${s}px "Inter Variable", system-ui, sans-serif`;
  g.font = font(100);
  const widest = Math.max(...lines.map((l) => g.measureText(l).width));
  const size = Math.min((h * 0.62) / lines.length, (100 * w * 0.86) / widest);
  g.font = font(size);
  g.textAlign = 'center';
  g.textBaseline = 'middle';
  lines.forEach((l, i) => g.fillText(l, w / 2, h / 2 + (i - (lines.length - 1) / 2) * size));
  const alpha = g.getImageData(0, 0, w, h).data;
  const points = [];
  for (let y = 0; y < h; y += gap)
    for (let x = 0; x < w; x += gap)
      if (alpha[(y * w + x) * 4 + 3] > 140) points.push({ x, y });
  return points;
}

// Each particle springs to its target and is pushed away from the pointer.
export function step(particles, pointer) {
  for (const p of particles) {
    let ax = (p.tx - p.x) * 0.05;
    let ay = (p.ty - p.y) * 0.05;
    const dx = p.x - pointer.x, dy = p.y - pointer.y;
    const d = Math.hypot(dx, dy) || 1;
    if (d < pointer.r) {
      const push = (1 - d / pointer.r) * 7;
      ax += (dx / d) * push;
      ay += (dy / d) * push;
    }
    p.vx = (p.vx + ax) * 0.84;
    p.vy = (p.vy + ay) * 0.84;
    p.x += p.vx;
    p.y += p.vy;
  }
}

How it works

  1. Sample the word. Draw the text once on a hidden canvas, then read its pixels and keep one point every few pixels wherever the letters are opaque. Those points are the targets.
  2. One particle per target. Each particle starts at a random spot with no speed.
  3. Spring home. Every frame, the particle accelerates toward its target in proportion to the distance, and friction (0.84) keeps it from overshooting forever.
  4. Push away from the pointer. Inside a radius around the pointer, add a push that grows as the particle gets closer. Lift the finger and the spring takes over again.
  5. Color by position. The gradient runs on each particle’s target x, so the word keeps its colors even while it is scattered.

Full source

Get the whole project as a zip

The demo page, every file and the export settings for the reel. It lands in your inbox, along with one new build a week.

You'll get a confirmation email first. One click to unsubscribe.

Make reels like this one

Reel Engine

The render pipeline behind every reel: HTML to MP4 at 1080×1920, 10 effects, 9:16 templates and a setup guide.

Coming soon

Subscribers hear first and get the launch price.

See what's inside