ascii-animation
This skill should be used when the user asks to "make an ASCII animation", "build a terminal/CLI intro or loader", "convert an image or video to ASCII art", "add an ASCII shader/post-effect to a canvas or Three.js scene", "create retro/hacker text-character motion", or "animate text characters with
By iart-ai · 733 installs
npx skills add iart-ai/web-animation-skills --skill ascii-animation
Source repository · Upstream listing
ASCII Animation
Render motion entirely with text characters. ASCII output is extremely lightweight, distinctive, and works in browsers ( <pre /canvas), terminals (ANSI), and over any 3D scene (Three.js AsciiEffect ).
When to use
Build terminal/CLI intros, loaders, banners, or a retro/hacker aesthetic.
Convert an image, video frame, or 3D scene into animated ASCII.
Add an ASCII post effect over an existing canvas/WebGL render.
Create generative text fields (plasma, sine waves, noise, tunnels).
Core concept: the brightness ramp
Map luminance (0..1) to a character whose ink density matches. Order characters dark to light. Pick the index with Math.round(lum (ramp.length 1)) .
Common ramps (dark to light):
Short (10): .: =+ %@
Medium (16): .'\ ^",:;Il!i <~+ ?][}{1)( /tfjrxnuvczXYUJCLQ0OZmwqpdbkhao MW&8%B@$ truncated — see table below.
Standard 70 level (Paul Bourke), best for photos:
$@B%8&WM oahkbdpqwmZO0QLCJUYXzcvunxrjft/\ ()1{}[]? +~< i!lI;:,"^ '. (reverse for dark on light).
Compute relative luminance from sRGB (perceptual):
Invert when drawing dark text on a light background: lum = 1 lum .
Character cell aspect correction (the 1 gotcha)
Monospace character cells are taller than wide — roughly 0.5 width:height . Sampling a square pixel grid produces a vertically stretched image. Correct by sampling fewer rows than columns : for a target of cols characters wide, use rows = Math.round(cols (imgH / imgW) fontAspect) where fontAspect ≈ 0.5 . Equivalently, when drawing to an offscreen canvas, set its height to cols aspect 0.5 .
Web rendering: <pre vs canvas
<pre + textContent : simplest. One string with \n per row. Fine up to ~120×60 chars at 30fps. Set white space: pre; font family: monospace; line height: 1; .
Canvas fillText : needed for per character color, larger grids, or 60fps. Draw each char at x cellW, y cellH . Faster than thousands of DOM nodes.
Generative <pre field (plasma):
Image / video to ASCII
Draw the source to a small offscreen canvas at cols × rows , read pixels with getImageContext().getImageData , then map each pixel to a character. For video, repeat per frame from a <video element.
For Node image→ASCII, see scripts/img to ascii.mjs .
3D to ASCII with Three.js
AsciiEffect wraps a renderer and renders any scene as ASCII into a DOM element. Render through the effect, not the renderer.
Terminal / CLI animation
Loop with ANSI escape codes: hide the cursor, move to home, print the frame, throttle to 12–24 fps. Show the cursor again on exit.
Use ESC + '2J' to clear the whole screen, ESC + 'H' for home (cheaper per frame than clearing). Color with \x1b[38;2;R;G;Bm (truecolor) and reset with \x1b[0m .
Deliver & verify (standalone HTML)
Packaged helper ( scripts/ ): scripts/seek shot.sh anim.html 0 1.5 3 freezes the ?t=N harness and screenshots each moment; scripts/contact sheet.sh sheet.png frame .png tiles them for one glance review. See scripts/README.md .
For a self contained web ASCII piece (generative field, image/video→ASCII, AsciiEffect scene) the deliverable is one HTML file that opens directly in a browser — the <pre /canvas, the ramp, and the rAF loop inline (Three.js from CDN if used). No build step. One file is the right tier; don't reach for a bundler. (Terminal/CLI pieces verify differently — capture stdout or a screenshot of the terminal.)
Output contract:
One .html file: the render target, the brightness ramp, and the requestAnimationFrame loop in one inline <script .
Drive frames from an injectable time (not Date.now() / performance.now() directly) and seed any randomness, so a frame is reproducible.
Seek harness — freeze the rAF loop on a deterministic frame. ?t=N renders exactly one frame at simulated time N instead of looping, so a screenshot is reproducible. Feed N where the loop reads time, and fix the seed:
Verify loop — render → freeze → screenshot → check: render at a few simulated times ( ?t=0 , ?t=1000 , ?t=2000 ), screenshot each, and check fidelity (ramp reads dark→light correctly, motion evolves) plus artifacts (vertical stretch from missing cell aspect correction, wrong invert on a light bg, clipped grid, FOUC before the monospace font loads — fonts settle the cell metrics, so wait). Any headless tool works:
Before you finish:
1. Opens standalone — no console errors, CDN (Three.js, if used) loads, monospace font applied.
2. ?t=N renders one deterministic frame (injected time + fixed seed), no live loop.
3. Screenshotted at 3 simulated times — matches the brief, no vertical stretch or wrong invert.
4. prefers reduced motion honored — stop/slow the rAF loop, show a static frame.
5. Easing is intentional — frame rate throttled on purpose (12–24fps for retro feel), ramp ordering deliberate.
Quick reference
Need Approach
Simple generative field <pre + textContent , sine/noise → ramp
Photo fidelity 70 level Bourke ramp, luminance from sRGB weights
Per char color / 60fps Canvas fillText per cell
Video <video → offscreen canvas → getImageData per frame
3D scene Three.js AsciiEffect.render(scene, camera)
Terminal ANSI \x1b[H home + throttle 12–24fps, hide cursor
Aspect fix rows = cols imgAspect 0.5
Reference files
references/rendering.md — Full brightness ramp tables (10/70/extended), pixel sampling math and cell aspect correction, <pre vs canvas tradeoffs with code, ANSI terminal frame loop with truecolor, and complete AsciiEffect wiring.
scripts/img to ascii.mjs — Runnable Node script that converts a PNG/JPG file to ASCII text, with cols , invert , and ramp flags.