Animated ASCII in asciicn moves slowly on purpose. A background should drift, not perform. Every renderer follows the same rules, and they come from ascii-core, so you get them for free when you build your own.
When a renderer moves
AsciiShader and AsciiGrid animate only while someone can see them. Their data-state attribute says what they are doing:
data-state | Meaning |
|---|---|
running | Drawing up to fps frames a second, 30 by default. |
paused | Scrolled out of view or in a hidden tab. Time stops, so it resumes where it left off. |
static | Motion is off. Reduced motion draws stillTime. animate={false} holds the current frame, or draws stillTime if it never moved. |
fallback | Nothing drawn yet. For AsciiShader, also when WebGL is unavailable. The fallback content shows instead. |
Time only advances while running, and a long frame (a tab switch, a debugger) counts as at most a tenth of a second, so motion never jumps.
Reduced motion
When the visitor turns on reduced motion in their system settings, every renderer draws one still frame and stops. stillTime picks which moment of the animation that frame shows, so you can choose a good one:
<AsciiGrid draw={drawScene} stillTime={14} />The switch is live: turning the setting on or off while the page is open takes effect at once. CRT flicker and glitch stop too. Interface pieces follow the same rule: their transitions become instant, and anything that loops holds still.
Speed and frame rate
speed scales time, and changing it never jumps the motion, because each frame adds its own slice of time. fps caps the frame rate. The default of 30 keeps slow drifts smooth at half the cost of 60. Raise it for fast motion such as particles, rain or anything you plan to record.
Your own loops
useFrameLoop is the loop both renderers use. Give it a callback and an element, and it calls you once per frame while that element is on screen:
const ref = useRef<HTMLDivElement>(null);
const state = useFrameLoop(
(time, delta) => {
// draw the frame for `time` seconds
},
{ target: ref, fps: 30, stillTime: 0 }
);Under reduced motion or with enabled: false, it calls the callback once with stillTime and a delta of 0. It returns "running", "paused" or "static", and only re-renders when that changes. usePrefersReducedMotion, useInView and useDocumentVisible are exported too.
In AsciiGrid, the same values arrive in the draw context: time is seconds scaled by speed, and delta is the scaled time since the last frame (0 for a still frame). Use time for anything that should look the same at a given moment, such as waves and orbits, and delta for simulations that step forward.
Easing and timing
For transitions in interface pieces, MOTION holds the shared values:
MOTION.ease; // "cubic-bezier(0.23, 1, 0.32, 1)", a strong ease-out
MOTION.fast; // 150ms, for presses and colour changes
MOTION.base; // 240ms, for small reveals
MOTION.slow; // 600ms, for large surfaces fading inHover effects use Tailwind's hover: variant, which only applies on devices that can hover, so a tap on a phone never leaves an element stuck in its hover state.