Animations & Transitions
Reference for PanelAnimations (layer keyframes and viewport moves) and the Transition object — the seven transition types, directions, timing, and easing.
The format describes motion in two places: within a panel (PanelAnimations — layer keyframes and a camera move across the artwork) and between panels or pages (Transition — the visual effect while navigating). Both are declarative.
PanelAnimations
Defined as $defs/PanelAnimations, set per panel at chapters[].panels.<panelId>.animations (also overridable via variants). A panel has one animation. It can hold layer keyframes (layers move, fade or change their filters), a viewport move (a Ken-Burns-style pan/zoom over the artwork), or both.
| Property | Type | Required | Constraints | Description |
|---|---|---|---|---|
name | string | No | — | Author-facing label. Not shown to readers. |
durationMs | integer | No | 0–120000 | Animation duration in milliseconds. |
loop | boolean | No | default false | Restart the animation when it ends. |
keyframes | AnimationKeyframe[] | No | up to 2000 items | Layer keyframes — see below. |
startViewportRect | NormalizedRect | No | x,y,w,h each 0–1 | Viewport at animation start, normalized to the panel. |
endViewportRect | NormalizedRect | No | x,y,w,h each 0–1 | Viewport at animation end. |
easing | enum | No | linear, ease, ease-in, ease-out, ease-in-out | Easing of the viewport move. |
Layer keyframes (AnimationKeyframe)
Added within format 1.6.0. Each keyframe sets one property of one layer at one point in time:
| Property | Type | Required | Constraints | Description |
|---|---|---|---|---|
layerId | Identifier | Yes | — | The layer of this panel to animate (layers[].id). |
property | enum | Yes | see the table below | What to animate. |
timeMs | integer | Yes | 0–120000 | Position on the panel's timeline in milliseconds. |
value | number | Yes | — | Value at that time, in the unit of the property. |
easing | enum | No | linear, ease, ease-in, ease-out, ease-in-out | Curve from this keyframe to the next one of the same track. Default linear. |
id | Identifier | No | — | Stable id for authoring tools. |
No other properties are allowed on a keyframe.
property | Value |
|---|---|
opacity | 0–1; replaces the layer's opacity. |
transform.x, transform.y | Offset from the layer's resting position as a fraction of the panel's width / height (0.1 = 10% right / down, negative = left / up). |
transform.scale | Scale factor around the layer's center (1 = unchanged). |
transform.rotation | Degrees clockwise around the layer's center. |
blur | Blur radius in px at a panel width of 1024 px; scaled with the rendered panel. |
brightness, contrast, saturate | Multiplier (1 = unchanged). |
Offsets and blur are relative to the panel, not device pixels, so an animation looks the same on every screen.
How keyframes play:
- Keyframes with the same
layerIdandpropertyform a track, ordered bytimeMs. - Before a track's first keyframe the layer holds the first value; after the last it holds the last value.
- Between two keyframes the value is interpolated with the
easingof the earlier keyframe. - Properties without keyframes are left as the layer defines them.
- The animation starts when the panel is shown and runs for
durationMs(or until the last keyframe, when that is later). Withloop: trueit restarts; otherwise it keeps its end state. - Players that honour a reduced-motion preference skip the motion and show the end state.
- A keyframe whose
layerIddoes not exist in the panel is ignored.
{
"animations": {
"name": "Robot rolls in",
"durationMs": 4000,
"loop": false,
"keyframes": [
{ "layerId": "character", "property": "transform.x", "timeMs": 0, "value": -0.4, "easing": "ease-out" },
{ "layerId": "character", "property": "transform.x", "timeMs": 3000, "value": 0 },
{ "layerId": "character", "property": "opacity", "timeMs": 0, "value": 0 },
{ "layerId": "character", "property": "opacity", "timeMs": 800, "value": 1 }
]
}
}
The character starts 40% of the panel width to the left, invisible, fades in over 0.8 s and rolls to its resting position over 3 s.
Player support. @panelwave/player plays layer keyframes and viewport moves from the release after 1.2.0 (merged, not yet published at the time of writing). Version 1.2.0 and earlier ignore both and show the static panel, as does any consumer that does not know them.
Viewport moves
startViewportRect and endViewportRect describe a camera move from one rectangle of the panel to another: the part of the panel the reader sees at the start and at the end of the animation. A NormalizedRect requires all four of x, y, w, h, each between 0 and 1.
{
"animations": {
"startViewportRect": { "x": 0, "y": 0, "w": 1, "h": 1 },
"endViewportRect": { "x": 0.3, "y": 0.2, "w": 0.4, "h": 0.4 },
"durationMs": 4000,
"easing": "ease-in-out"
}
}
This starts on the full panel and slowly zooms into the region at (0.3, 0.2) sized 40% × 40% — a classic dramatic push-in.
How a viewport move plays:
- The move runs from the start rect to the end rect over
durationMs, eased byeasing, on the same timeline as the layer keyframes;looprestarts both. - A missing rect is the whole panel: an end rect alone is a push-in, a start rect alone a pull-back.
- The rect is fitted into the panel box with a uniform scale — the artwork is never stretched — and centred. Near an edge the rect sits off-centre rather than showing empty space.
- Everything anchored to the artwork moves together: layers, hotspots and speech bubbles. The panel box clips the result.
- Without
durationMsthere is no travel: the end rect is shown as a static framing. - Reduced motion shows the end rect at once.
Layers can add depth to these moves with parallaxDepth — see Layers.
Transition
Defined as $defs/Transition. Transitions describe how the view changes while navigating, and appear in several places:
- Graph edges —
chapters[].graph.edges[].transition(the transition used when following that edge) - Hotspot
goToactions —action.transition(see Hotspots) - Page transitions —
pages[].transitions.in/pages[].transitions.out(entering/leaving a page) - Format presets —
settings.outputPresets.<format>.defaultTransition(per-output-format default, see Settings)
All properties are optional:
| Property | Type | Constraints | Description |
|---|---|---|---|
type | enum | none, cut, fade, slide, zoom, push, cover | The transition effect (7 types). |
dir | enum | left, right, up, down | Direction for directional types (slide, push, cover). |
durationMs | integer | 0–60000 | Duration in milliseconds. |
easing | enum | linear, ease, ease-in, ease-out, ease-in-out | Easing function. |
Transitions on edges
The most common placement is on a graph edge, so different paths through the story can feel different:
{
"graph": {
"entry": "p1",
"edges": [
{
"from": "p1",
"to": "p2",
"transition": { "type": "slide", "dir": "left", "durationMs": 300 }
},
{
"from": "p2",
"to": "p3-dream",
"condition": { "==": [{ "var": "state.dreaming" }, true] },
"transition": { "type": "fade", "durationMs": 800, "easing": "ease-in-out" }
}
]
}
}
Which transition applies
- Navigation triggered by a hotspot
goToaction uses thetransitiondeclared on that action. - Navigation along a graph edge uses the edge's
transition. - When the chosen navigation declares no transition, the active output format's
defaultTransitionfromsettings.outputPresetsserves as the fallback.
What can animate — summary
| Mechanism | Where | What it does |
|---|---|---|
PanelAnimations keyframes | panels.<id>.animations.keyframes | Layers move, fade, scale, rotate or change filters over time. |
PanelAnimations viewport move | panels.<id>.animations | Viewport pan/zoom across the panel artwork. |
Transition | edges, hotspot actions, pages, output presets | Effect between panels/pages during navigation. |
parallaxDepth | layer property | Per-layer depth offset during viewport motion — see Layers. |
| Video layers | panels.<id>.layers[] / panels.<id>.video[] | Real motion content — see Video. |
Related pages
- Graph — edges and conditions
- Chapters & Pages — page
transitions.in/out - Settings —
outputPresetsand default transitions