Player reference

svgmo-player.js plays svgmo files (.json) in a web page. It adds:

  • the <svg-mo> element, and
  • an Svgmo object for use from scripts.

One file, about 30 KB (11 KB gzipped), no dependencies. See Getting started for installation.

A page with every example below running is served by the app at /player-demo.html.

The <svg-mo> element

<script src="svgmo-player.js"></script>
<svg-mo src="logo.json" autoplay loop></svg-mo>

Attributes

Attribute Values Default Meaning
src URL none The svgmo file to load. Changing it loads the new file.
autoplay present / "false" off Start playing once loaded. Only applies when trigger is not set.
loop present / "false" on Repeat forever. loop="false" plays once and stops on the last frame.
speed number above 0 1 Playback rate. 2 is twice as fast, 0.5 is half speed.
direction "reverse" or "-1" forward Play backwards.
trigger "hover", "click", "visible" none What starts playback. See below.
respect-reduced-motion present off If the visitor's system asks for reduced motion, show the last frame and do not animate.
aria-label text none Description for screen readers. The drawing is exposed as an image.

Triggers

trigger Behaviour
hover Plays while the pointer is over the element. On leave, stops and returns to the first frame.
click Each click toggles play and pause.
visible Starts when the element scrolls into view.

With a trigger set, autoplay is ignored.

Size

  • With no size from the page, the element is as wide as the animation's artboard, in pixels, and never wider than its container.
  • It always keeps the artboard's proportions.
  • Give it a width (style="width: 300px", width: 100%, a CSS class) and the height follows.

Off-screen behaviour

The element pauses itself when it is scrolled out of view and resumes when it is visible again. This saves CPU and battery and needs no setup.

Inline data instead of a file

To avoid a second request, put the animation inside the tag:

<svg-mo autoplay>
  <script type="application/json">
    { "format": "svgmo", "v": 1, "meta": { "generator": "svgmo" }, "comp": { ... } }
  </script>
</svg-mo>

src wins when both are present.

Methods

const el = document.querySelector("svg-mo");

el.play();     // start, or resume
el.pause();
el.stop();     // pause and return to the first frame
el.seek(30);   // jump to frame 30
el.player;     // the player behind the element (below)

Events

The element fires DOM events:

Event When event.detail
ready The file is loaded and the first frame is drawn
play Playback starts
pause Playback stops, for any reason
loop A loop restarts
complete A non-looping animation reaches its end
error The file could not be loaded or is not valid message text
el.addEventListener("complete", () => console.log("done"));
el.addEventListener("error", (e) => console.warn(e.detail));

Per-frame updates are not sent as DOM events. Use el.player.on("frame", fn).

The script API

For cases where a tag is not enough.

Svgmo.load(options)

Loads an animation into an element and returns the player.

const player = await Svgmo.load({
  container: "#stage",      // element, or a CSS selector
  src: "logo.json",      // URL of the file
  autoplay: true,
  loop: false,
});
Option Type Default Meaning
container element or selector required Where to draw
src string URL of an svgmo file
data string or object The animation itself, instead of src
autoplay boolean false Play once loaded
loop boolean true Repeat
speed number 1 Playback rate
direction 1 or -1 1 Forward or backward

Give src or data. The promise resolves after loading. If the file fails to load the promise still resolves; listen for error or check player.isLoaded.

Unlike the element, a player created this way does not size its container, does not pause off-screen and has no triggers. The drawing fills the container's width and height, keeping proportions.

new Svgmo.Player(container, options)

The same player without loading anything yet. Call player.load(source) yourself.

Player methods

Method Meaning
load(source) Load a URL, JSON text or a parsed object. Returns a promise of true or false.
play() Start or resume. From the end, starts over.
pause() Stop where it is.
stop() Pause and return to the first frame.
toggle() Play if paused, pause if playing.
seek(frame) Jump to a frame. Fractions are allowed.
seekTime(seconds) Jump to a time.
seekProgress(p) Jump to a position from 0 (start) to 1 (end).
setSpeed(n) Change the rate.
setLoop(bool) Turn looping on or off.
setDirection(1 | -1) Forward or backward.
on(event, fn) Listen. Returns a function that removes the listener.
destroy() Stop, remove the drawing and all listeners.

Player properties

Property Meaning
frame Current frame (fractional while playing)
totalFrames Length in frames
fps Frames per second of the file
duration Length in seconds
isPlaying true while playing
isLoaded true once a file is loaded
size { width, height } of the artboard, or null
lastWrites Attributes changed by the last drawn frame (see Performance)

Player events

ready, play, pause, loop, complete, error, and frame (called on every drawn frame with the frame number).

const off = player.on("frame", (frame) => {
  progressBar.value = frame / player.totalFrames;
});
// later
off();

Recipes

Scroll-driven animation

<svg-mo id="story" src="story.json" style="width: 100%"></svg-mo>
<script>
  const el = document.getElementById("story");
  addEventListener("scroll", () => {
    const max = document.documentElement.scrollHeight - innerHeight;
    el.player.seekProgress(max > 0 ? scrollY / max : 0);
  }, { passive: true });
</script>

Play once, then hold, then replay on click

<svg-mo id="tick" src="tick.json" autoplay loop="false"></svg-mo>
<script>
  const tick = document.getElementById("tick");
  tick.addEventListener("click", () => { tick.stop(); tick.play(); });
</script>

A button whose icon animates on hover

<button class="like">
  <svg-mo src="heart.json" trigger="hover" loop="false" style="width: 24px"></svg-mo>
  Like
</button>

Scrubber and transport buttons

const el = document.querySelector("svg-mo");
play.onclick = () => el.play();
pause.onclick = () => el.pause();
scrub.oninput = () => { el.pause(); el.player.seekProgress(Number(scrub.value)); };
el.player.on("frame", (f) => { scrub.value = f / el.player.totalFrames; });

React

Custom elements work in JSX. Load the script once, then:

export function Logo() {
  return <svg-mo src="/logo.json" autoplay loop style={{ width: 200 }} />;
}

For methods, use a ref: ref.current.play(). In TypeScript, declare the tag in JSX.IntrinsicElements to avoid a type error.

Swap the animation

el.setAttribute("src", "other.json");   // element
await player.load("other.json");        // script API

A player that was playing keeps playing the new file.

Accessibility

  • Add aria-label to describe the animation, or aria-hidden="true" if it is decoration.
  • Add respect-reduced-motion so visitors who ask for less motion get a still picture.
  • With trigger="click", wrap the element in a <button> so it is reachable by keyboard.

Performance

The player builds the drawing once and then, on each frame, changes only what is different.

  • Still layers cost nothing. A layer with no animation is drawn once and never touched again, however many points it has.
  • Moving layers update only what moves. A shape that slides or spins gets a new transform; its outline is not rewritten. A colour change rewrites only the colour.
  • A layer that follows an animated parent is treated as moving.
  • Shape morphing (a path with path keyframes) does rewrite that one outline every frame; that is the costly case. Keep morphing shapes to a sensible point count.
  • Off-screen <svg-mo> elements pause on their own.
  • Playback follows the display refresh rate; the file's frame rate sets the speed, not how often it draws.
  • If a browser tab was in the background, playback continues from where it was rather than jumping ahead.

Measured in Chromium, script and DOM time per frame (painting not included):

Scene Rebuild everything each frame This player
300 still shapes of 120 points each (36,000 points) plus 6 moving layers about 22 ms about 0.04 ms, 6 attribute writes
The small sample scene (3 animated layers) about 0.04 ms about 0.02 ms

At 60 frames a second a frame has about 16 ms, so the first row is the difference between dropping frames and having time to spare.

player.lastWrites tells you how many attributes the last frame changed; 0 means nothing on screen changed. It is useful when checking why an animation is heavy.

Loading and hosting

  • src is fetched with fetch, so the file must be reachable over HTTP(S) and allowed by CORS when it is on another domain.
  • Opening a page from disk (file://) blocks the request in most browsers. Use a local web server, or inline the data.
  • Any JSON or text content type works. Servers already send .json as application/json.

Errors

A failed load fires error with a message and leaves the element empty. Messages include:

Message Meaning
Could not load <url> (404) The file was not found, or the server refused
The file is not valid JSON. The response is not JSON, often an HTML error page
This is a Lottie file, not an svgmo file. Export svgmo JSON from the editor instead
This file uses format version N; this reader understands up to 1. Update the player. The file is newer than the player
Other text such as layers must be an array The file is damaged or hand-edited incorrectly

Earlier names

<vector-motion> and VectorMotion.load() were the first names of the tag and the script object. Both still work and behave the same as <svg-mo> and Svgmo.load(). The player also plays files saved with the old .svgmo and .vmotion extensions.

Browser support

Uses custom elements, shadow DOM, IntersectionObserver, fetch and inline SVG: all current browsers. Tested in Chromium; Safari and Firefox have not been run.