Scribble flood
- Source
- arlan.me/vault ↗
- Author
- Arlan
- Licence
- MIT
Loading…
A grey dot writes a thick scribble that swells until its ink floods the card, then draws again in the opposite colours.
Code
"use client";
import { CanvasStudy, easeIn, prog, rng, type Frame, type Painter } from "./stage";
export const BG = "#ffffff";
const CYCLE = 4.4;
const DRAW = 2.8;
const N = 420;
function scribble(seed: number): [number, number][] {
// a steered random walk: loops and doubles back like a hand scribbling
const r = rng(seed * 977 + 13);
let x = 0.2 + r() * 0.6;
let y = 0.25 + r() * 0.5;
let heading = r() * Math.PI * 2;
let turn = 0;
const pts: [number, number][] = [];
for (let i = 0; i < N; i++) {
turn = turn * 0.92 + (r() - 0.5) * 0.16;
const toC = Math.atan2(0.5 - y, 0.5 - x);
let d = toC - heading;
d = Math.atan2(Math.sin(d), Math.cos(d));
const far = Math.hypot(x - 0.5, y - 0.5);
heading += turn + d * Math.max(0, far - 0.3) * 0.9;
x += Math.cos(heading) * 0.014;
y += Math.sin(heading) * 0.014 * 1.3;
pts.push([x, y]);
}
return pts;
}
export function create(): Painter {
let seed = -1;
let pts: [number, number][] = [];
return ({ ctx, w, h, t }: Frame) => {
const n = Math.floor(t / CYCLE);
const u = t - n * CYCLE;
if (n !== seed) {
seed = n;
pts = scribble(n);
}
const ground = n % 2 ? "#111111" : "#ffffff";
const ink = n % 2 ? "#ffffff" : "#111111";
ctx.fillStyle = ground;
ctx.fillRect(0, 0, w, h);
const drawn = prog(u, 0.15, DRAW);
const swell = easeIn(prog(u, 0.15 + DRAW + 0.1, 1.1));
const base = Math.min(w, h) * 0.055;
const lw = base + swell * Math.hypot(w, h) * 1.1;
const count = Math.max(1, Math.floor(drawn * (N - 1)));
ctx.strokeStyle = ink;
ctx.lineWidth = lw;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
for (let i = 0; i <= count; i++) {
const [x, y] = pts[i];
if (i === 0) ctx.moveTo(x * w, y * h);
else ctx.lineTo(x * w, y * h);
}
if (drawn > 0) ctx.stroke();
// the grey pen dot writing the line
if (swell <= 0 && drawn < 1) {
const [x, y] = pts[count];
ctx.fillStyle = "#8f8f8f";
ctx.beginPath();
ctx.arc(x * w, y * h, base * 0.42, 0, Math.PI * 2);
ctx.fill();
}
};
}
export default function ScribbleFlood() {
return (
<CanvasStudy
create={create}
background={BG}
still={2.2}
label="A grey dot writes a thick scribble that swells until it floods the card, then the drawing restarts in the opposite colours."
/>
);
}
Prompt
Build "Scribble flood", a canvas animation. What it does: A grey dot writes a thick scribble that swells until its ink floods the card, then draws again in the opposite colours. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS. Technique: an HTML canvas, a requestAnimationFrame loop, pointer tracking. It sits on a light background, in a 16:10 frame. Respect prefers-reduced-motion with a still state, pause work when the element is off screen, and keep it accessible. Original: https://www.arlan.me/vault (Arlan, MIT).