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.
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
- 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.
- One particle per target. Each particle starts at a random spot with no speed.
- Spring home. Every frame, the particle accelerates toward its target in proportion to the distance, and friction (
0.84) keeps it from overshooting forever. - 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.
- 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.