Skip to content

Playback settings & triggers

Change how an animation plays — its length, loops, direction, what starts it — without going back to the editor. Everything about when and how it plays lives in one place, the document’s animator block, and every player lets you override it at runtime from component props or the player API. This page is the reference for those fields and the overrides.

The editor writes the same block from its playback panel; if you only want to set the defaults there, see Set default playback settings & triggers.

A document with its animator block. (The comments are explanatory; JSON does not allow comments, so a real file has none.)

{
"type": "svg",
"viewBox": "0 0 400 400",
// Everything about WHEN and HOW the animation plays lives here
"animator": {
"timeline": {
"type": "clock",
"duration": 2000,
"iterations": "infinite",
"direction": "alternate",
"trigger": { "startOn": "scrollIntoView", "outAction": "pause", "scrollIntoViewThreshold": 0.5 }
}
},
"children": [
{
"type": "circle",
"id": "ball",
"cx": 0, "cy": 0, "r": 40, "fill": "#0087ff",
"animate": {
"translate": {
"keyframes": [
{ "time": 0, "value": [200, 60], "easing": [0.33, 0, 0.67, 0.33] },
{ "time": 2000, "value": [200, 340] }
]
}
}
}
]
}

The same bouncing ball as in the web player, now two seconds per bounce and waiting until half of it has scrolled into view.

The timeline — what advances the playhead

Section titled “The timeline — what advances the playhead”

animator.timeline says what drives the animation’s progress, exactly like a Web Animations API timeline. Its type picks one of three, mirroring WAAPI’s DocumentTimeline / ScrollTimeline / ViewTimeline:

timeline.typeThe playhead follows…
clock (default)wall time — something starts it (the trigger), and it has the playback dynamics below
scrolla scroll container’s offset — Scroll-driven playback
viewthe SVG’s journey through the viewport — Scroll-driven playback

Each type carries only the fields that mean something for it — a scrubbed timeline has no trigger or delay, and the format gives them no slot there. Omitting timeline entirely means a plain clock.

The engine settings stay on animator itself; timing and the playback dynamics live in the timeline:

FieldValuesDefaultMeaning
timeline.durationms1000length of one pass of the timeline. Keyframe times are absolute offsets within it
frameRatefpsuncappedtarget rate for the frame-loop engine only
modeauto · waapi · framesautothe engine — Engine mode
timeline.delayms0wait this long, then start. A negative value skips ahead instead: -500 starts right away from the frame at 0.5 s, as if the animation had already been running for half a second
timeline.iterationsnumber · "infinite"1how many times the whole document timeline repeats
timeline.directionnormal · reverse · alternate · alternate-reversenormalalternate ping-pongs on every other iteration
timeline.fillforwards · backwards · both · noneforwardswhat is shown outside the active time: forwards holds the last frame after the end; backwards shows the first frame during the delay; none reverts to the static SVG
timeline.trigger.onFinishhold · resetholdafter a natural finish: keep the end state (per fill), or snap back to the start

Per-property loops vs iterations. There are two kinds of repetition, and they work at different levels. iterations repeats the whole document — every element, from the first keyframe to the last. A single property can also loop on its own: a segment of its own keyframes repeats until it fills the document’s duration, while everything else plays through once (see JSON format → Per-property loops). The property loop is applied first, when the document is prepared; iterations then repeats the result. So both can be used at once, and one runs inside the other: a wheel whose rotation loops, inside a document set to infinite iterations, keeps spinning during every iteration.

ModeWhat runs the animation
auto (default)the Web Animations API — played by the browser itself, so it stays smooth even while the page is busy — with an automatic fallback to the frame loop when the document animates something WAAPI cannot express (path morphing, gradient geometry, filters, text on a path, …)
waapiWeb Animations API only
framesa requestAnimationFrame loop that writes attributes every frame; honours frameRate; universal browser support

Leave it on auto unless you need a guarantee — for instance frames for path morphing in Safari < 18.5. React Native ignores mode (playback is always native-driven).

The trigger block — inside the clock timeline — says what starts the animation and what happens when that condition ends. The editor writes it from its Start setting; every player honours it:

"timeline": { "type": "clock", "trigger": { "startOn": "mouseOver", "outAction": "reset" } }
startOnStarts when…Editor label
load (default)the animation is displayedOn load
scrollIntoViewthe element becomes visible; scrollIntoViewThreshold says how much of it must be on screen first: 0 (default) any part, 0.5 half of it, 1 all of itWhen visible
mouseOverthe pointer enters the elementOn mouse over
clickthe element is clicked (a second click applies outAction)On click
programmaticnever by itself — you call play()Manually from JS

outAction says what happens when the trigger condition ends (pointer leaves, scrolled out, second click):

outActionEffect
continue (default)keep playing
pausepause where it is; the next trigger resumes
resetjump back to the start
reverseplay backwards to the start

Where triggers work:

  • Every player — web, React, Vue and React Native — supports all of them, with one exception: React Native has no mouseOver, because there is no hover on a touch screen.
  • Pre-rendered SVG + CSS animation + JS triggers supports all of them too. The editor writes a few lines of script into the file for this; no library is involved.
  • Pre-rendered SVG + CSS animation (no script at all) supports load, and mouseOver through CSS :hover. click and scrollIntoView cannot be done in pure CSS, so in this flavour they behave like load — the animation starts as soon as it is shown. See Pre-rendered SVG.

Example: playback/override-webpnpm example:docs, then open #playback/override-web. Example: playback/override-reactpnpm example:docs, then open #playback/override-react.

Web player — edit the object before handing it over (the player reads animator once at creation):

<div id="box" style="width: 300px; height: 300px"></div>
import { createAnimator } from '@pixodesk/svg-animator-web';
const doc = await (await fetch('/bouncing-ball.json')).json();
doc.animator = { ...doc.animator,
timeline: { type: 'clock', iterations: 'infinite', trigger: { startOn: 'programmatic' } } };
const a = createAnimator({ data: doc, container: '#box' });
a.play();

React / Vue / React Native — the components take props with the same names as the fields of the document’s animator block, and a prop you pass replaces that one field for that one component; the rest of the document is untouched: duration, delay, iterations, direction, fill, mode, frameRate replace the fields of animator; startOn, outAction, scrollIntoViewThreshold replace the fields of animator.trigger (see each package page). Note that the components switch the trigger to programmatic whenever you use play / pause / apiRef / time, so only autoplay mode uses the trigger saved in the file.

"animator": { "debugInstName": "heroBanner" } makes the player publish its API object as window.heroBanner, so you can drive a live instance from the browser console — heroBanner.pause(), heroBanner.setCurrentTime(500), and so on. Purely a debugging convenience; remove it (or leave it — it has no other effect) for production files.

In development. Scroll-driven playback is not finished yet: the fields below may change, and not every combination works in every player. Time-driven playback — the default — is not affected.

Instead of playing on a clock, the animation can follow the scroll position — the playhead moves as the user scrolls: scroll down and the animation goes forward, scroll back up and it goes backward, stop and it stays on that frame. This is the model of CSS scroll-driven animations. Choose Timeline → scroll in the editor’s playback panel, or set it in the document:

"animator": {
// The playhead follows the SVG's journey through the viewport instead of the clock;
// `duration` is the keyframe span the scroll range maps onto.
"timeline": { "type": "view", "duration": 3000, "range": { "start": { "phase": "entry", "fraction": 0 }, "end": { "phase": "exit", "fraction": 1 } } }
}

timeline: { "type": "view" } alone means “show the whole animation, first frame to last, as the SVG travels across the viewport — the scroll position, not the clock, decides which frame is on screen”; type: "scroll" follows the scroll container’s offset instead. The clock fields (trigger, delay, "infinite") have no slot in these timelines. The rest of the object tunes it:

timeline.ValuesMeaning
typeview · scrollprogress = the SVG’s journey across the scrollport, or the scroll container’s offset ratio
axisblock (default) · inline · x · ywhich axis; block = vertical in normal writing mode
sourcenearest (default) · rootfor type: scroll — the nearest scrollable ancestor, or the document
subjectparent · scroller · a CSS selectorfor type: viewwhose journey is measured (default: the <svg> itself). parent is what makes a pinned section work
range.start / range.end{ phase, fraction }the slice of the journey mapped to 0–100 %; phasecover (default) · contain · entry · exit · entry-crossing · exit-crossing; fraction is a position within that phase, 0 = its start, 1 = its end
iterationsnumberthe animation repeats this many times across the range (finite only — "infinite" cannot map onto a range)
smoothingmscatch-up lag — the playhead eases toward the scroll position instead of snapping (smoother under momentum scrolling)
pintrue · { align, top, distance }hold the canvas still on screen while scrolling moves the animation forward and back (position: sticky); aligntop/center/bottom, top in px, distance in viewport heights creates the scroll travel
enginecustom (default) · nativewho computes progress: the player’s own measurement (identical everywhere) or the browser’s ScrollTimeline (falls back automatically when unsupported)

Support: the web player (both engines, and therefore React and Vue), and the SVG + JS animation export. Not yet: the CSS export or React Native. The complete “scrollytelling” pattern is subject: "parent" + pin: true inside a tall section.