Vue — @pixodesk/svg-animator-vue
Use this in a Vue 3 or Nuxt app: drop in the component, pass it the JSON, and it renders the animation and controls its playback. It wraps the web player and renders the SVG through Vue’s virtual DOM, so it is SSR-safe and Nuxt-ready. It mirrors the React component feature for feature, so the two guides read the same.
npm install @pixodesk/svg-animator-vue<script setup lang="ts">import { SvgAnimationPlayer } from '@pixodesk/svg-animator-vue';import animation from './animation.json';</script>
<template> <SvgAnimationPlayer :doc="animation" playUsingActionTriggers /></template>The component renders the document’s root <svg> directly — there is no wrapper element. To
set its size, give the element that contains it a width and height (or put style on the
component itself — see the props table); the SVG keeps its proportions.
Control modes
Section titled “Control modes”Every way to make it play starts with play: playUsingActionTriggers (the document’s triggers, or ones
you pass), play / pause (your state), showAtProgress / showAtTime (your
position). Set props from more than one group and the highest-priority one wins —
showAtProgress / showAtTime → play / pause → playUsingActionTriggers — and the
component warns, naming both props and the winner. The template ref is not a mode: it is
available in every mode and never changes which one you are in. React, Vue and React Native all
resolve this the same way, from one rule in core.
Nothing plays until a prop or option names the driver (playUsingActionTriggers, play,
showAt…). A file embedded with no code around it — the <div data-px-animation-src> tag, the
CSS wrapper, a standalone SVG file — has no other possible driver, so there the embedding is that
naming and the file plays as saved.
In every mode but playUsingActionTriggers the file’s own triggers are switched off — core’s
withoutTriggerDriver replaces them with an empty block, so nothing starts the animation by
itself (a note on the control props).
Play using action triggers (playUsingActionTriggers)
Section titled “Play using action triggers (playUsingActionTriggers)”Example:
vue/play-using-action-triggers—pnpm example:docs, then open#vue/play-using-action-triggers.
The simplest mode: the component starts the animation the way the file says it should — on load, on hover, on click — once enough of it is on screen.
<script setup lang="ts">import { SvgAnimationPlayer } from '@pixodesk/svg-animator-vue';import animation from './animation.json';</script>
<template> <SvgAnimationPlayer :doc="animation" playUsingActionTriggers /></template>playUsingActionTriggers alone uses the triggers saved in the document — their event lists and the
visibility gate (Triggers — WHEN it plays).
Bind an object instead and that is the whole actionTriggers block to use for this mount — a
replacement, not a merge: the lists you leave out are simply not listed, never the file’s values.
How each action looks stays in the file’s actions, which the actions prop adjusts — see
Playback overrides.
<template> <!-- plays while hovered; when the pointer leaves it finishes the current iteration, then rests --> <SvgAnimationPlayer :doc="animation" :playUsingActionTriggers="{ start: 'hoverIn', stop: 'hoverOut' }" :actions="{ stop: { completeIteration: true } }" />
<!-- a click toggles stopped → playing → paused → playing (the state rule picks the list) --> <SvgAnimationPlayer :doc="animation" :playUsingActionTriggers="{ start: 'click', pause: 'click' }" />
<!-- a click starts it, the next click stops it --> <SvgAnimationPlayer :doc="animation" :playUsingActionTriggers="{ start: 'click', stop: 'click' }" /></template>Controlled time (showAtProgress / showAtTime)
Section titled “Controlled time (showAtProgress / showAtTime)”Example:
vue/controlled-time—pnpm example:docs, then open#vue/controlled-time.
Use these when your code owns the position — a slider, a scroll offset, a step in a walkthrough. The component renders exactly that frame and never plays on its own.
<script setup lang="ts">import { ref } from 'vue';import { SvgAnimationPlayer } from '@pixodesk/svg-animator-vue';import animation from './animation.json';const time = ref(0);</script>
<template> <SvgAnimationPlayer :doc="animation" :showAtTime="time" /> <input type="range" min="0" max="2000" v-model.number="time" />
<!-- a fixed frame by FRACTION of the whole timeline — that is `showAtProgress`, not `showAtTime` (milliseconds, so `:showAtTime="0.5"` would be half a millisecond in) --> <SvgAnimationPlayer :doc="animation" :showAtProgress="0.5" /></template>Changing the value moves the existing player to the new position — nothing is recreated. The
read twins on the template ref are getCurrentProgress() and getCurrentTime().
Declarative playback — play, pause
Section titled “Declarative playback — play, pause”Example:
vue/declarative—pnpm example:docs, then open#vue/declarative.
play names a state, not a command. true calls the built-in start, false the built-in
stop, and HOW each one looks is the file’s actions block — the start, stop and finish
behaviors and the stop’s completeIteration — which the actions prop adjusts and which the
triggers and the ref honour too
(Actions — how start, stop and finish look).
play false performs the stop action (actions.stop: reset or rewind) — it does not pause;
freeze with pause.
With the defaults, a ONE-SHOT document (a single pass) plays to the end on true and holds,
and snaps back on false; a LOOPING document (iterations > 1 or 'infinite') runs on true
and snaps to rest on false — or finishes the current iteration first with
:actions="{ stop: { completeIteration: true } }". A change always moves from the current frame, and a mount
renders the pose without animating. pause freezes the current frame immediately and only
means something while play is true (pause without play warns,
PX1502). The rule is shared with the React and React Native
components (resolveDeclarativePlayback in core). One-shot verbs (playOnce(), finish())
live on the template ref.
<script setup lang="ts">import { ref } from 'vue';import { SvgAnimationPlayer } from '@pixodesk/svg-animator-vue';import animation from './animation.json';const hovered = ref(false);const busy = ref(false);</script>
<template> <!-- the trash-can lid: opens on hover, plays BACK closed on leave, from wherever it is --> <div @mouseenter="hovered = true" @mouseleave="hovered = false"> <SvgAnimationPlayer :doc="animation" :play="hovered" :actions="{ stop: 'rewind' }" /> </div>
<!-- a spinner: runs while busy; when busy ends it finishes the current turn, then rests --> <SvgAnimationPlayer :doc="animation" :play="busy" :actions="{ stop: { completeIteration: true } }" /></template>The other recipes — overlay art that appears instantly and retreats animated
({ start: 'jumpToFinished', stop: 'rewind' }), a splash that replays
on every click ({ start: 'restart' }) — are written out on the React page;
the same actions objects work here.
Imperative API (template ref)
Section titled “Imperative API (template ref)”Example:
vue/imperative—pnpm example:docs, then open#vue/imperative.
The component exposes the playback API on its template ref, so it is available in every mode:
<script setup lang="ts">import { ref } from 'vue';import { SvgAnimationPlayer, type PxAnimationPlayerHandle } from '@pixodesk/svg-animator-vue';import animation from './animation.json';const player = ref<PxAnimationPlayerHandle | null>(null);</script>
<template> <SvgAnimationPlayer :doc="animation" ref="player" /> <button @click="player?.play()">Play</button> <button @click="player?.pause()">Pause</button> <button @click="player?.stop()">Stop</button></template>The ref holds core’s PxAnimationPlayerHandle — the one handle name every framework package
exports, so the three cannot drift apart. play(), pause() and stop() take no arguments:
they call the built-in actions, so how a start or a stop looks is the file’s actions, and the
handle follows it exactly as the triggers and the play prop do.
interface PxAnimationPlayerHandle { isPlaying(): boolean; play(): void; // paused → resume; otherwise the configured start (`actions.start`) pause(): void; // hold the current frame stop(): void; // the configured stop (`actions.stop`) — after the current // iteration when its `completeIteration` is true reset(): void; // reset NOW, unconditionally — back to the start state jumpToFinished(): void; // jump to the end and hold it playOnce(): void; // ONE fresh pass from the start, whatever `iterations` say; // the end pose follows `actions.finish` setPlaybackRate(rate: number): void; // 1 normal, 2 double, negative = reverse; 0 is rejected // with a warning — use pause() getDuration(): number | null; // ONE pass of the content window, ms — the slider // maximum nobody hard-codes (× iterations = the run) getCurrentTime(): number | null; // ms from the start of the whole run, every iteration included setCurrentTime(time: number): void; // seek, ms; clamped to the run getCurrentProgress(): number | null; // the same position as 0–1 of the whole run — the read // twin of the `showAtProgress` prop setCurrentProgress(p: number): void; // seek, 0–1 of the whole run}With none of playUsingActionTriggers / showAtProgress / showAtTime / play / pause set,
the first frame renders statically and the ref is your only control.
Playback overrides
Section titled “Playback overrides”The same document can play differently in each place you mount it. Three props, the same on every player (Overriding from a player), and the document you passed is never modified:
timelineOptions— corrections to the file’sanimator.timelineblock, minus itsactionTriggersandactions: an object shaped like the file —iterations,delay,playDuration, the gate numbers; whoever needs more edits the document.actions— corrections to the file’stimeline.actions:{ stop: 'rewind' }changes how a stop looks and leaves every other setting as the file has it.playUsingActionTriggers—trueplays on the file’s triggers; an object is the wholeactionTriggersblock to use instead (Play using action triggers). To have your code drive instead, pass no control prop at all — the file’s triggers are then switched off and the template ref is the driver.
Merge or replace — the one rule for the three:
timelineOptions, actions | patches — objects merge key by key, values replace, null deletes |
a playUsingActionTriggers object | replaces the file’s actionTriggers as a whole — its lists belong together |
<template> <!-- The file loops twice and plays on load; here it loops forever and waits for play() — without `playUsingActionTriggers` the file's triggers are switched off, so only your code starts it. --> <SvgAnimationPlayer :doc="animation" :timeline-options="{ iterations: 'infinite' }" ref="anim" />
<!-- These triggers instead of the file's, and a smooth retreat on hover-out --> <SvgAnimationPlayer :doc="animation" :playUsingActionTriggers="{ start: 'hoverIn', stop: 'hoverOut' }" :actions="{ stop: 'rewind' }" /></template>null deletes a key, so the default its absence means comes back
(:timeline-options="{ delay: null }").
actionTriggers and actions are not keys of timelineOptions — only playUsingActionTriggers carries triggers
and only actions the action settings, so every key is reachable exactly one way. The out-of-view
behavior goes in actions (:actions="{ outOfView: 'continue' }"); the gate
numbers inViewThreshold and inViewDebounce are timeline keys and go in timelineOptions
(the visibility gate).
To repeat the whole animation, pass :timeline-options="{ iterations: 'infinite' }" — there is no
loop prop. A loop inside a file means something narrower: it repeats part of one property’s
keyframes (per-property loops).
Full merge rules — including what happens when the override changes the kind of timeline — are in Playback & triggers → How the merge works.
Only doc is required. The file already carries the timing and the triggers you set in the
editor; every other prop is optional and, when passed, adjusts the file’s value for this one
component.
The React set, minus playerRef / className / style — a template ref and Vue’s attribute
inheritance cover those:
const SvgAnimationPlayer: DefineComponent<{ doc: PxDocumentInput; // required — the animation; a .json import passes as-is
// Playback override — deep-merged over the file (Playback overrides above) timelineOptions?: PxTimelinePatch; // the timing: the `timeline` block minus `actionTriggers` and `actions` actions?: PxActionsPatch; // how start, stop, finish and outOfView look: { stop: 'rewind' }
// Control — the highest-priority one that is set picks the mode (Control modes above) playUsingActionTriggers?: boolean | PxActionTriggersInput; // true: the file's own triggers; an object: these triggers instead — // { start, stop, pause }, each one event or a list of the events // that list may name ('load' | 'hoverIn' | 'hoverOut' | 'click'); // resume is derived from them play?: boolean; pause?: boolean; // the STATE (true calls `start`, false calls `stop` — how each looks // is the file's `actions`) / freeze now showAtProgress?: number; // a frame at 0–1 of the whole run showAtTime?: number; // a frame at this ms
// Diagnostics stay PROPS, not events: an event handler always exists, which would silence // the console fallback. onWarn = it plays, but something was ignored, degraded or misspelled; // onError = this instance will not play (the API at a glance) onWarn?: (d: PxDiagnostic) => void; onError?: (d: PxDiagnostic) => void; suppressWarnings?: boolean; suppressErrors?: boolean;}>;Anything else you put on <SvgAnimationPlayer> — class, style, any attribute — ends up on
the <svg> element it renders (standard Vue attribute inheritance). So to set the animation’s
size, either put style="width: 300px; height: 300px" on the component itself, or give those
dimensions to the element that contains it — the SVG keeps its proportions either way.
Events
Section titled “Events”| Event | When |
|---|---|
ready | the player exists and the SVG is rendered — once, asynchronously; the push twin of isReady() |
play | the animation started playing — for the first time, or resumed after a pause |
pause | playback paused at the current frame — via the pause prop, the API’s pause(), a pause trigger, or the visibility gate |
reset | back at the start pose — reset(), a stop or finish whose preset is reset, the end of a rewind, the gate’s outOfView: reset |
finish | the animation reached its end — it played all its iterations, or finish() was called. Does not fire when playback is stopped early |
destroy | the player was thrown away: the component unmounted, or you passed a different doc and a new player was built for it |
stop | the built-in stop action was performed — a stop trigger, play false or stop(); now, or after the iteration it waited for. A pause is pause, the end finish, a reset() only reset |
onWarn and onError are props, not events. With an event, Vue would treat the message as
handled even when nobody listens, so it would never reach the console. As props, leaving them
out means exactly that — and the message goes to the console as usual.
Each diagnostic is { code, kind, data?, message, error? }. code is a number you can switch on — its description is on the codes page, which message links to; data carries the specifics. kind says who can act on it:
document (repair the file) · host (fix the page) · platform (the browser could not do it;
the player degraded) · usage (fix the props you passed) · internal (report it to us). So you
can route rather than just log — surface document problems in a build check, for instance. Once
you know what a document has to say and tolerate it, suppressWarnings keeps it out of the console.
<script setup lang="ts">import { SvgAnimationPlayer } from '@pixodesk/svg-animator-vue';import animation from './animation.json';const onDone = () => console.log('finished');const onStop = () => console.log('stopped');</script>
<template> <SvgAnimationPlayer :doc="animation" playUsingActionTriggers @finish="onDone" @stop="onStop" /></template>Passing a different doc throws the old player away and builds a new one; the old instance
emits reset and destroy on its way out.
CSS-flavor SVGs — SvgCssAnimationPlayer
Section titled “CSS-flavor SVGs — SvgCssAnimationPlayer”Example:
vue/css-loader—pnpm example:docs, then open#vue/css-loader.
For a pre-rendered SVG + CSS animation file imported with
vite-svg-loader (or any loader that yields a
component), this wrapper adds the triggers and the visibility gate. It renders a <div> of its
own around your SVG component — that is what SvgCssAnimationPlayer becomes on the page — and
starts, pauses or resets the animation by switching the file’s CSS classes on that <div>:
<script setup>import { SvgCssAnimationPlayer } from '@pixodesk/svg-animator-vue';import AnimationSvg from './animation.svg'; // vite-svg-loader</script>
<template> <SvgCssAnimationPlayer :actionTriggers="{ start: 'hoverIn', pause: 'hoverOut' }" style="width: 400px; height: 400px"> <AnimationSvg /> </SvgCssAnimationPlayer></template>actionTriggers is the JSON player’s block — the same lists over the same events, read by
the same rules (Triggers — WHEN it plays) —
and the gate is the same gate. Absent actionTriggers play on load; an empty block lists nothing.
// The SVG goes in the default slot; every other attribute (class, style, …) lands on the wrapper div.const SvgCssAnimationPlayer: DefineComponent<{ actionTriggers?: PxActionTriggersInput; // the requests: { start, stop, pause }, each one event or a // list of the events that list may name ('load' | 'hoverIn' | // 'hoverOut' | 'click'); resume is derived from them actions?: Pick<PxActions, 'outOfView'>; // only `outOfView` applies: { outOfView: 'pause' } or // { outOfView: { behavior } } — 'pause' (default) | 'continue' | // 'reset' while nobody can see it inViewThreshold?: number; // 0–1 of the SVG on screen before it may run; default 0.5; 0 = any visible pixel inViewDebounce?: number; // ms it must hold first; default 150}>;Accepted limits of a class toggle — how the moves look stays in the file’s CSS, so here:
- the start preset acts as
continue— a class cannot restart keyframes or jump to their end; - the stop preset acts as
reset— a class cannot play keyframes backwards; - the stop’s
completeIteration,showWhenStoppedand the finish preset are the file’s own CSS and are not changed from here.
⚠️ Don’t put the SAME SVG file on a page twice. You can have as many
<SvgCssAnimationPlayer>on a page as you like, each with a different file. What does not work is the same file twice: the imported component is the file’s markup, element ids included, so two copies share the same ids and their masks and gradients cross over. To show one animation several times, use the JSON component instead — the player gives every copy its own ids (read more).
Only the pure CSS flavor works this way (loaders strip or refuse <script>); flavors with
scripts should be inlined as raw HTML, or use JSON.
API reference
Section titled “API reference”Everything is spelled out above: SvgAnimationPlayer under Props and Events,
the handle under Imperative API, SvgCssAnimationPlayer under
CSS-flavor SVGs. Modes, highest priority wins:
showAtProgress / showAtTime → play / pause → playUsingActionTriggers → static — the
same rule as React and React Native, conflicts warned the same way; a change to doc’s content
(compared deeply) or to an override prop re-creates the player. The callbacks and diagnostics
are the shape every player shares — the API at a glance.
The package exports the two components, SvgAnimationPlayer and SvgCssAnimationPlayer, the
handle type PxAnimationPlayerHandle, and the document type PxDocument (for casting a JSON
import — TypeScript); nothing else.
Example
Section titled “Example”Every section above links to its running example in
examples/docs-examples. Each example is a small standalone
page, and they are all collected in one app: a list of every example down the side, with the
selected one running next to it. Run pnpm example:docs from the repository root to open it,
then pick an example from the list — or jump straight to one by its address in the URL, like
#vue/play-using-action-triggers. Each example has a test that runs on every build.