Docs
Flipbook Foundry is an animation library where every frame is a glyph. Actions are baked into fonts at build
time; at runtime a figure is one character that gets swapped for the next drawing. The API borrows
framer-motion's vocabulary. If you know animate, variants and stagger, you know this.
npm i flipbook-foundry
// Svelte
import { Stage, Figure, TextFlow, FlipbookConfig } from 'flipbook-foundry/svelte';
// React
import { Stage, Figure, TextFlow, FlipbookConfig, useAnimate } from 'flipbook-foundry/react';
// Anything else
import { createStage, animate, stagger } from 'flipbook-foundry';
// Fonts load from jsDelivr, pinned to your installed version, and only the
// chunks your figures use. To self-host, copy node_modules/flipbook-foundry/fonts/
// to your public folder and call setFontBase('/fonts/') once at startup.
// Preload with getLibrary().load(['regular', 'bold']).Page
The simplest way in. Write a sentence and drop actions into it as {walk}. pretext lays
the paragraph out once, with words in your font and figures in the Flipbook Stick font, all at text size.
Every drawing of an action has the same width, so animating a figure only swaps its character and the
layout never moves.
<Page
text={'Bob {walk} left the house. Sue {wave} waved. The dog {run} gave chase.'}
font="400 20px Georgia" // any CSS font for the words
figureScale={1.3} // figure size relative to the text (default 1.3)
align="center" // or left
phase={({ x, width }) => x / width} // loops start by position: a wave
bind:page // page.figures[i] are Characters you can direct
/>
{trip>get-up>dust-off} a sequence, looped
{idle|sneak|run} a figure that may switch between these
(room is reserved for the widest, so the line never moves)
a newline starts a new paragraphFigure
The motion component. On its own it renders inline: a single character in a line of text that you can style with CSS like any other. Inside a Stage it draws on the canvas instead, so thousands can share one frame.
<Figure
animate="walk" // action or variant to play; change it to play another
initial="sit-idle" // shown first; routes from here into animate
exit="bow" // played before removal from a Stage
transition={{ onTwos: true }}
whileHover="wave" // inline only (Stage figures: use stage.hitTest)
whileTap="jump"
x={120} y={300} // Stage px; y is the ground line under the feet
size={64} // font size in px; the figure is ~0.7 of it
facing={-1} // 1 faces right, -1 mirrors
layer="bold" // regular | bold | limbs | limbs-bold
body={{ char: 'A', font: '700 10px Georgia' }} // a letter for a body
onAnimationComplete={(action) => …}
onEvent={(event, action) => …} // 'hit', 'land', 'release'…
/>Transition
Passed as transition, inside a variant, or as animate()'s options.
fps | number | Frames per second. Defaults to the action's authored rate (usually 24). |
speed | number | Rate multiplier on top of fps. |
onTwos | boolean | Hold each drawing for two frames, the hand-drawn flipbook cadence. |
delay | seconds | Wait before starting. In animate(), may be a stagger() function. |
repeat | number | Extra plays. Loops default to Infinity, one-shots to 0. |
repeatType | 'loop' | 'reverse' | 'mirror' | Restart, or ping-pong the drawings. |
repeatDelay | seconds | Hold between repeats. |
phase | 0–1 | Start a loop partway through, to desynchronise a crowd. |
inPlace | boolean | Ignore baked root motion: treadmill instead of travel. |
interrupt | 'settle' | 'cut' | How to leave what's playing. 'settle' (default) finishes a one-shot or plays a loop to its next exit, then settles to rest. 'cut' switches at once. |
route | boolean | Route through hub transitions first (default true). |
Variants
Name your states, then animate between names. A variant is an action name or a target with its own
transition, a destination x, a facing, and what to do then.
const moods = {
calm: 'idle',
happy: { action: 'cheer', transition: { repeat: 2 }, then: 'calm' },
leave: { action: 'walk', x: 900, then: 'wave' }, // travel to x, then wave
};
<Figure variants={moods} animate={mood} />Stage
A canvas world. Each figure is one fillText of its current glyph, depth-sorted by its ground line and skipped when offscreen; a stage that's scrolled out of view stops painting. A thousand figures draw in a few milliseconds.
<Stage
background={null} // or a colour; null leaves the canvas transparent
layers={['regular']} // fonts to load before first draw
onUpdate={(frame, stage) => …} // game logic, every frame
onBeforeDraw={(frame, stage) => …} // draw terrain under the figures
onAfterDraw={(frame, stage) => …}
>
…figures, text flows…
</Stage>
// The Stage instance (bind:stage in Svelte, onReady / useStage in React):
stage.add({ x, y, size, facing }) // → Character
stage.remove(character, 'bow') // exit action, then gone
stage.addProp({ text: 'A', font: '700 40px Georgia', attach: { to: c, anchor: 'handR' } })
stage.hitTest(x, y) // topmost Character or null
stage.toStage(clientX, clientY) // pointer → stage space
stage.camera = { x, y, zoom } // scroll and zoom big maps
stage.stats // { fps, characters, drawn, updateMs, drawMs }Things & props
Glyphs don't have to be people. The Things font holds props and set pieces, some with frames of their own,
so a sack can fill and a hole can deepen. A held tool is drawn at the figure's hand and turned by the
forearm angle baked into every frame, so one shovel works with every action. Events like scoop, toss, grab and release are the beats you sync to.
// Things are glyphs too: props and set pieces in the Flipbook Stick Things font.
// shovel · broom · sack (12 fullnesses) · hole (12 depths) · pile (12 sizes) · wall · clod
await getLibrary().load(['regular', 'things']);
const sack = stage.addThing({ name: 'sack', x: 600, y: ground, size: 120 });
const wall = stage.addThing({ name: 'wall', x: 900, y: ground, layer: 'front' }); // hides who's behind
const shovel = stage.addThing({ name: 'shovel', size: 120 });
stage.hold(shovel, digger, 'R'); // tools turn with the forearm, every frame
stage.hold(sack, thief, 'L', true); // sacks hang; true draws it behind the figure
stage.drop(sack); // lands under the hand
sack.frame++; // a drawing fuller
// Throw anything along an arc: a stolen letter, a clod of earth.
const letter = stage.addProp({ text: 'A', font: '700 54px Georgia', x, y });
await stage.fly(letter, { to: [sack.x, sack.y - 40], height: 90, spin: 1.5 });
// Real letters as objects: where each one is, so you can take it.
flow.glyphs(); // [{ index, char, x, y, width }, …] then flow.setText(without it)TextFlow
Paragraphs in any font, laid out by pretext. Measurement happens once, so wrapping is arithmetic and can run every frame around moving figures.
<Stage style="height: 420px">
<TextFlow
text={copy}
font="400 22px Georgia" // any CSS font; measured once by pretext
x={0} y={0} width={720} lineHeight={32}
padding={10} // breathing room around figures
avoid // re-wrap around every figure, every frame
/>
<Figure animate="run" x={0} y={160} size={64} />
</Stage>
// Without components:
const flow = createTextFlow({ text, font, x, y, width, lineHeight });
flow.layout(obstacles); // Rect[] → fragments, pure arithmetic
flow.draw(ctx);
flow.ledges(); // word tops: places to stand, sit or hanganimate() & stagger()
import { animate, stagger } from 'flipbook-foundry';
animate(bob, 'jump'); // → controls (thenable)
await animate(bob, 'walk', { x: 400, then: 'wave' }); // travel, then settle
animate(crowd, 'cheer', { delay: stagger(0.04, { from: 'center' }) });
// Sequences, with framer-motion's `at`:
animate([
[bob, 'walk', { x: 260 }],
[sue, 'walk', { x: 340, at: '<' }], // same time as previous
[[bob, sue], 'high-five'], // after both arrive
[bob, 'cheer', { at: '+0.3' }], // 0.3s after
]);
const c = animate(bob, 'dance');
c.stop(); await c.finished;Character
The state behind every figure. It never draws; renderers read glyph.
const c = createCharacter({ x: 0, y: 300, size: 64, animate: 'idle' });
c.animate('trip'); // interrupts, routes hubs, resolves when done
c.pose('jump', 14); // freeze on a drawing
c.on('hit', () => shake()); // baked events: hit, land, release, grab…
c.on('frame', ({ action, frame }) => …);
c.anchor('handR'); // [x, y] stage px — where to put the letter
c.bounds(); // ink box, for hit-tests and text flow
c.glyph; // the current character to draw
c.use(wander(), greet()); // behaviours
// Compose: actions play in order; rest-pose routes (get-up, stand-up…)
// are inserted for you. Loops play one cycle each.
c.sequence(['sneak', 'trip', 'dust-off', 'wave'], { loop: true });Behaviors
Small brains for crowds. They run once per frame and only ever call animate().
import { wander, greet } from 'flipbook-foundry';
stage.add({ x, y }).use(
wander({
area: [0, 1200],
gaits: ['walk', 'walk', 'jog', 'skip'],
idles: ['idle', 'look-around', 'yawn', 'dance'],
pause: [1, 4],
clumsiness: 0.1, // chance of a trip on the way
}),
greet({ radius: 70, actions: ['wave', 'high-five', 'bow'] }),
);
// A behaviour is just (character, stage, now) => void. Write your own:
const flee = (c, stage) => { if (near(c, cat)) c.animate({ action: 'sprint', x: 0 }); };React hooks
const animate = useAnimate(); // animate() that stops on unmount
const stage = useStage(); // enclosing Stage (null until fonts load)
useStageFrame((frame, stage) => …); // per-frame logic
const reduced = useReducedMotion(); // honours prefers-reduced-motion
<Figure ref={setBob} animate="idle" /> // ref → CharacterFlipbookConfig
Defaults for every Figure below it, like framer-motion's MotionConfig.
<FlipbookConfig
family={stick} // figure family (default: stick)
transition={{ onTwos: true }}
layer="bold"
size={48}
reducedMotion="user" // 'user' | 'always' | 'never' — freezes figures on frame 0
>
…
</FlipbookConfig>Families
A family is a set of figure fonts and the manifest of actions drawn in them. Each one is its own package; flipbook-foundry ships with @flipbook-foundry/stick as the default, so you only
name a family to switch or self-host. Families load their fonts from jsDelivr, pinned to the installed
version.
import stick from '@flipbook-foundry/stick';
import { getLibrary, setDefaultFamily, setFontBase } from 'flipbook-foundry';
setDefaultFamily(stick); // the family used when none is given
<Stage family={stick}>…</Stage> // a Stage's Figures draw in its family
<Page family={stick} text={…} />
<Figure family={stick} animate="wave" /> // inline figures
getLibrary(stick).names(); // that family's actions
setFontBase('/fonts/', stick); // self-host its fontsHubs & routing
Every one-shot starts and ends on a rest pose, called a hub: stand, crouch, sit, lie, perch (on a ledge) or hang (from one). Ask a lying figure to wave and it plays get-up first. Ask a standing one to sit-idle and it plays sit-down. Ledge actions move the ground line for you: sit-on puts a figure on a
ledge, hop-off drops it back to the floor.
Loops pass through the same rest pose. Each one has exit frames, at most half a second apart, with a short
settle into rest baked from each. A new action waits for the next exit, settles, and starts; a one-shot
finishes first. Loops start on their calmest frame. Nothing cuts mid-pose unless you pass { interrupt: 'cut' }.
Action reference
locomotion
walk | 24f @24 | loop | An easy, bouncy stroll. | ||
run | 16f @24 | loop | Leaning, arm-pumping run with a flight phase. | ||
jog | 20f @24 | loop | Easy jog with a small float between steps. | ||
sprint | 12f @24 | loop | All-out sprint, deep lean and high knees. | ||
sneak | 32f @24 | loop | Hunched cartoon sneak, high careful steps. | ||
tiptoe | 16f @24 | loop | Dainty, upright tiptoe on stretched legs. | ||
march | 24f @24 | loop | Proud parade march, high knees, stiff arm swing. | ||
skip | 24f @24 | loop | Happy step-hop skip with big arm pumps. | ||
backpedal | 24f @24 | loop | Nervous walk backward, arms up in front. | ||
skid-stop | 26f @24 | stand → stand | contact | Brake out of a run: lean-back skid, dust, wobble, stand. | |
start-run | 16f @24 | stand → stand | Rock back, coil, burst into a run (ends on run frame 0). | ||
shuffle-step | 16f @24 | stand → stand | One small step forward, as in a queue. | ||
sneak-off | 32f @24 | loop | Big cartoon tiptoe: knee to the chin, hold, toe down, paws up. |
aerial
hop | 12f @24 | loop | launch land | Springy little hop in place, looping. | |
jump | 34f @24 | stand → stand | launch land | Wind up, spring high, tuck, land with a squash. | |
leap | 34f @24 | stand → stand | launch land | Running split leap that sails forward. | |
frontflip | 34f @24 | stand → stand | launch land | Tucked frontflip, sticks the landing. | |
backflip | 34f @24 | stand → stand | launch land | Tucked backflip, sticks the landing. | |
fall | 16f @24 | loop | Flailing, tumbling fall (loops while airborne). | ||
land | 26f @24 | stand → stand | land | Hard landing: crunch, dazed wobble, stand. |
pratfall
trip | 42f @24 | stand → lie | contact land | Snag, flailing dive, belly-skid, flip over onto the back. | |
tumble | 32f @24 | stand → stand | land | Forward roll that travels, pops up to standing. | |
slip | 28f @24 | stand → lie | contact land | Banana-peel: feet fly up, thud onto the back. | |
stumble | 34f @24 | stand → stand | contact | Catch a toe, windmill, recover. | |
faceplant | 42f @24 | stand → crouch | land | Topple forward like a plank, splat, push up to a crouch. | |
topple | 30f @24 | stand → lie | impact | Stiff as a board, falls backward like a domino. |
transition
sit-down | 22f @24 | stand → sit | contact | Lower down, hands back, plop onto the floor. | |
stand-up | 22f @24 | sit → stand | Pull the knees in, rock forward, rise. | ||
lie-down | 28f @24 | stand → lie | contact | Sit, then ease back flat. | |
get-up | 32f @24 | lie → stand | contact | Kick, rock up, scramble to the feet. | |
crouch-down | 12f @24 | stand → crouch | Drop into a crouch with a little overshoot. | ||
rise | 14f @24 | crouch → stand | Spring up from a crouch, settle tall. | ||
climb-up | 28f @24 | hang → stand | Hang → pull up → knee over → stand on the ledge. Ledge is y=0; ends 190 forward. | ||
hop-off | 20f @24 | perch → stand | land | Scoot forward off a ledge onto the floor 180 below. Ledge is y=0. | |
sit-on | 20f @24 | stand → perch | contact | Back onto a ledge behind and perch. Ledge is y=0; floor is 180 below. |
idle
idle | 32f @12 | loop | Breathing, settling weight, alive but still. | ||
look-around | 48f @24 | stand → stand | Shades the eyes, peers forward, then cranes up. | ||
tap-foot | 24f @24 | loop | Hands on hips, impatient foot tapping. | ||
yawn | 40f @24 | stand → stand | Big full-body stretch-yawn. | ||
sleep | 32f @12 | loop | Asleep on the back, slow breathing. | ||
fidget | 40f @24 | stand → stand | Scratches head, shifts weight, scuffs a foot. | ||
sit-idle | 32f @12 | loop | Sitting on the floor, knee bobbing, glancing around. | ||
perch-idle | 24f @24 | loop | Sitting on a ledge, legs dangling and swinging. | ||
hang-idle | 32f @24 | loop | Hanging from a ledge by both hands, legs swinging. | ||
type | 24f @24 | loop | key | Hunched over an invisible keyboard, pecking away. | |
sweat | 32f @24 | loop | Nervous: wipes the brow, tugs the collar, glances about. | ||
lean-back | 32f @12 | loop | Leaning back on a wall, arms folded, ankles crossed. | ||
hang-swing | 32f @24 | loop | Hanging from a ledge, swinging the legs like a pendulum. | ||
search | 40f @24 | loop | Bent over, hand shading the eyes, scanning the ground. |
gesture
wave | 40f @24 | stand → stand | Big, friendly, whole-body wave. | ||
point | 32f @24 | stand → stand | point | Wind back and jab a finger forward, leaning in. | |
beckon | 24f @24 | loop | Come-here curl of the arm, head cocked. | ||
shrug | 30f @24 | stand → stand | Who knows? Palms up, head tipped. | ||
clap | 16f @24 | loop | clap | Enthusiastic applause. | |
salute | 30f @24 | stand → stand | Snappy military salute. | ||
bow | 40f @24 | stand → stand | Deep, theatrical bow. | ||
think | 32f @12 | loop | Hand to chin, pondering. | ||
facepalm | 40f @24 | stand → stand | hit | Hand to face. Sigh. | |
peek | 36f @24 | stand → stand | Leans out head-first as if round a corner, holds, snaps back. | ||
dust-off | 32f @24 | stand → stand | Brushes off shoulder and knees, then a small proud nod. | ||
peek-out | 48f @24 | stand → stand | out in | Hiding behind a wall: lean out, look about, duck back, peek again lower. | |
wave-hello | 64f @24 | stand → stand | Turn to face the viewer, wave, turn back. |
emote
cheer | 20f @24 | loop | land | Jumping for joy, arms in a V. | |
laugh | 16f @24 | loop | Doubled over, belly-shaking laughter. | ||
cry | 24f @12 | loop | Face in hands, sobbing. | ||
panic | 12f @24 | loop | Arms flailing, running on the spot. | ||
angry | 16f @24 | loop | stomp | Fists clenched, stomping tantrum. | |
dance | 32f @24 | loop | Four-beat groove: disco point, arm roll, shimmy. | ||
flex | 36f @24 | stand → stand | Double-biceps pose with pumps. | ||
shiver | 8f @24 | loop | Hunched and hugging, shivering. | ||
victory | 30f @24 | stand → stand | land | Leaping fist-pump, landing with a YES. | |
exhausted | 48f @24 | stand → stand | Doubles over, hands on knees, gasping — then recovers. | ||
huddle | 16f @24 | loop | Freezing: arms wrapped round, shivering, stamping from foot to foot. | ||
cover | 16f @24 | loop | Arms over the head against the rain, hunched and hurrying in place. | ||
wobble | 32f @24 | loop | Teetering on an edge, arms windmilling, one foot up for balance. | ||
rub-hands | 16f @24 | loop | Scheming villain hand-rub, hunched and bouncing. |
interaction
push | 32f @24 | loop | Leaning hard into something, shoving it forward. | ||
pull | 24f @24 | loop | Leaning back and hauling, stepping backwards. | ||
carry | 28f @24 | loop | Walking with something held overhead. | ||
lift | 40f @24 | stand → stand | grab lifted | Squat, grab, and hoist something overhead. | |
throw | 32f @24 | stand → stand | release | Overhead wind-up and throw. | |
catch | 28f @24 | stand → stand | catch | Ready, catch, absorb the impact. | |
kick | 28f @24 | stand → stand | hit | Big wind-up and a long snapping kick. | |
punch | 22f @24 | stand → stand | hit | Chambered straight punch. | |
high-five | 24f @24 | stand → stand | hit | Up high! Slap and recoil. | |
lean | 32f @12 | loop | Casually propped against a wall. | ||
knock | 30f @24 | stand → stand | hit | Knock-knock-knock, then listen. | |
dig | 32f @24 | loop | scoop toss | Shovel in hand: drive, stamp, lever, and pitch the dirt over the shoulder. | |
scoop | 28f @24 | stand → stand | grab | Bend, snatch something off the floor, and peer at it. | |
toss-back | 22f @24 | stand → stand | release | Fling something back over the shoulder without looking. | |
scoop-toss | 36f @24 | stand → stand | grab release | Snatch something off the floor and pitch it over the shoulder in one go. | |
drag | 28f @24 | loop | Hunched, heavy walk hauling a sack behind. | ||
erase | 20f @24 | loop | rub | Scrub away at something in front with an eraser, overhead down to the knees and back. |