Checker whip
- Source
- arlan.me/vault ↗
- Author
- Arlan
- Licence
- MIT
Loading…
A nine-row checkerboard whips sideways row by row as new scenes slide in, changing colour worlds and sometimes tearing odd rows against even.
Code
"use client";
import { CanvasStudy, easeInOutExpo, prog, type Frame, type Painter } from "./stage";
export const BG = "#1c3fd6";
const ROWS = 9;
const SCENE = 1.5; // seconds per scene
const PER_WORLD = 3; // scenes per colour world
type World = { tones: string[]; tear: boolean };
const WORLDS: World[] = [
{ tones: ["#ff47ad", "#1c3fd6", "#0b1f7a", "#f4efe6"], tear: false },
{ tones: ["#5ee8ff", "#0066ff", "#003399", "#ffffff"], tear: true },
{ tones: ["#e3dac6", "#c96a4a", "#2b2b2b", "#f2c14e"], tear: false },
{ tones: ["#151515", "#f2f2f2", "#ff3b30", "#7a7a7a"], tear: true },
{ tones: ["#c8f560", "#1a7f4b", "#0d3b24", "#f7f3e8"], tear: false },
];
function scene(n: number) {
const world = WORLDS[Math.floor(n / PER_WORLD) % WORLDS.length];
const i = n % PER_WORLD;
const T = world.tones;
return { a: T[i % 4], b: T[(i + 1) % 4], rect: T[(i + 2) % 4], world };
}
export function create(): Painter {
return ({ ctx, w, h, t }: Frame) => {
const rowH = h / ROWS;
const cell = rowH;
const n = Math.floor(t / SCENE);
const u = t - n * SCENE;
const cur = scene(n);
const next = scene(n + 1);
const rectW = Math.round((w * 0.42) / cell) * cell;
const rectX = Math.round((w - rectW) / 2 / cell) * cell;
const drawRow = (r: number, s: ReturnType<typeof scene>, ox: number) => {
const y = r * rowH;
if (ox >= w || ox <= -w) return;
ctx.save();
ctx.beginPath();
ctx.rect(Math.max(0, ox), y, Math.min(w, w + ox) - Math.max(0, ox), rowH + 0.5);
ctx.clip();
const cols = Math.ceil(w / cell) + 1;
for (let c = 0; c < cols; c++) {
ctx.fillStyle = (r + c) % 2 ? s.b : s.a;
ctx.fillRect(ox + c * cell, y, cell + 0.5, rowH + 0.5);
}
if (r >= 3 && r <= 5) {
ctx.fillStyle = s.rect;
ctx.fillRect(ox + rectX, y, rectW, rowH + 0.5);
}
ctx.restore();
};
// the incoming world decides how the rows move
const sweepDir = n % 2 ? -1 : 1;
for (let r = 0; r < ROWS; r++) {
const k = easeInOutExpo(prog(u, SCENE - 0.75 + r * 0.045, 0.42));
const dir = next.world.tear ? (r % 2 ? 1 : -1) : sweepDir;
const off = dir * k * w;
drawRow(r, cur, off);
drawRow(r, next, off - dir * w);
}
};
}
export default function CheckerWhip() {
return (
<CanvasStudy
create={create}
background={BG}
still={1.1}
label="A nine-row checkerboard whips sideways row by row as new checkered scenes slide in, changing colour worlds and sometimes tearing odd rows against even."
/>
);
}
Prompt
Build "Checker whip", a canvas animation. What it does: A nine-row checkerboard whips sideways row by row as new scenes slide in, changing colour worlds and sometimes tearing odd rows against even. 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).