Baktiyar Ablaikhan

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).