Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 16.3Advanced ~23 min · shot 3 of 6

Playing and Controlling dotLottie

In one line

new DotLottie({ canvas, src }) loads a file onto a canvas. Then you play named segments, change speed and loop, and listen for events. Bad input is mostly ignored quietly, and a file or WASM that cannot load fires loadError.

0/6 · 0%

Think of it like this

A DVD player with chapters. You load the disc, press play, jump to a named chapter, slow it down, and the player tells you when a chapter ends. If you press play before a disc is in, nothing happens and nothing breaks. The dotLottie player works like that, with markers as chapters.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

segment
A range of frames, [start, end], that the player plays instead of the whole file.
load event
The moment the file is parsed and ready. Before it, controls do nothing.
drawing buffer
The canvas's own pixel grid (its width and height attributes), separate from its CSS size.
devicePixelRatio
How many real screen pixels make one CSS pixel (1 on a basic screen, 2 or 3 on sharp phones).
WASM
WebAssembly: compiled code the browser runs next to JavaScript. The player's drawing engine is a WASM file.
loadError
The event that fires when the file or the player's WASM cannot be loaded.

Step by step

01Create the player, wait for load

The constructor starts both downloads and returns. The first example logs ready (the WASM is up) and then load (the file is parsed). Only then does it read markers() and the frame count.

02Press greet, then pause

Each button sets a marker and calls play(). Pause keeps the frame, so a later play() carries on from there. Try pause in the middle of the greeting, then press greet again.

03Slow it down

Use the timeline's 0.1× button, and the page's own 0.25× button. They are different things: the first slows the page's clock, the second asks the player to run slower.

04Read the error text

The second example creates a player for a missing file, a non-Lottie file, and one it starts too early. Its output shows every real message, and which wrong calls were ignored.

05Resize the canvas

The third example makes two canvases bigger. Only the one with autoResize (or after resize()) gets a bigger drawing buffer. Without it, the browser stretches the old pixels and the mascot goes soft.

Try it yourself

  1. 1

    Watch loop and complete

    In the player example press idle (loops) and wait a few seconds: the console gets event: loop each time the 2-second segment wraps and never event: complete. Press blink and complete appears once.

  2. 2

    Slow the blink to a quarter speed

    Press 0.25×, then blink. The 0.5 s blink takes about 2 s: in our run complete arrived 2,005 ms after play at 0.25× (real time), and 518 ms after at normal speed. Pause with the timeline and step to look at the frames.

  3. 3

    Try an invalid speed

    In the errors example change setSpeed(2) to setSpeed(0.5): the printed speed becomes 0.5 because setSpeed(0) and setSpeed(-1) after it are ignored.

Code & diagrams

A player with buttons and events

Press greet (it plays once and fires `complete`), blink, and idle (it loops, so the console shows `loop`). The timeline shows no track for the canvas: the player draws on its own loop, but pause and step still stop and advance it.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

event: ready
event: load
markers: greet 0-45, blink 45-60, idle 60-120
frames 120, duration 4 s, speed 1
What goes wrong, in the player's own words

The `[error]` line is the library writing to the console before it fires `loadError`. The speed, frame, segment and marker lines show invalid calls that were ignored without an exception.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

play() before load: isPlaying false, isLoaded false
Failed to load animation data from URL: /data/anim/nope.lottie. Error: Failed to fetch animation data from URL: /data/anim/nope.lottie. 404: Not Found
missing file: Error: Failed to load animation data from URL: /data/anim/nope.lottie. Error: Failed to fetch animation data from URL: /data/anim/nope.lottie. 404: Not Found
not a Lottie file: Error: failed to load
speed after 2, 0, -1: 2
frame after 30, 500: 30
segment after (60, 20): [0,119]
marker after 'nope': '', segment [0,119]
play() after destroy: isLoaded false
A canvas that grows

Both canvases started at 60 × 60 pixels. After the page made them 120 CSS pixels wide, only `autoResize` followed. The left one stays soft until `resize()` is called. On a screen with `devicePixelRatio` 2 the buffers would be twice as large.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

drawing buffers at the start: 60x60 and 60x60
after growing to 120 CSS px: default 60x60, autoResize 120x120
after pa.resize(): 120x120
devicePixelRatio here: 1

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget the marker

In the player example, delete the line p.setMarker(what);. Press Run, then press blink.

terminal
$ Press blink
── what you'll see ──
The status line says `playing blink [0,119]`: the whole 4-second file plays from the start (lamp pops up, beam on, blinks, idle), not the half-second blink.

Myth vs fact

Myth

The player throws an error when I give it a wrong value.

Fact

It ignores most of them. setSpeed(0), setFrame(500) and a bad segment all did nothing in our runs. Only loading reports an error.

Myth

The Motion Timeline shows every animation on the page.

Fact

It tracks CSS animations, the Web Animations API and the libraries it hooks. A canvas player draws on its own loop, so it has no track, though the page clock still drives it.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    renderConfig.freezeOnOffscreen is true by default, so the player already stops when its canvas scrolls away. mascot.js (Topic 16.6) adds its own IntersectionObserver as well, so it can also pause a half-played greeting and resume it, and so the player itself loads late.

  • ▸

    DotLottieWorker exists in the package: it moves rendering into a Web Worker (it needs the worker file served from your own origin). We did not measure its effect.

  • ▸

    markers() in 0.81.0 returns { name, start, end } while the shipped .d.ts says { name, time, duration }. If you use TypeScript, a cast or your own type is needed; this is a real mismatch measured in this phase.

Remember this

  1. 1

    The problem this solves: a designer's file is one long timeline, but a page needs pieces of it at different moments: wave once on arrival, blink on hover, loop quietly while idle. The player gives you those controls, so one file serves many moments.

  2. 2

    Create it. new DotLottie({ canvas, src: "/data/anim/headlight.lottie", autoplay: false, loop: false }) starts fetching the file and the WASM at once. Other options: data (a JSON string, object or ArrayBuffer instead of a URL), marker, segment: [start, end], speed, mode, layout, renderConfig. The constructor returns immediately; nothing can play until the load event.

  3. 3

    Play pieces. setMarker("greet") makes a marker the active segment. setSegment(10, 20) sets frames directly. play(), pause() (keeps the frame) and stop() (back to the start) control it, setFrame(n) shows one frame, and setSpeed(x), setLoop(true) and setMode("bounce") shape playback. markers() returns objects with name, start and end; the installed type file says time and duration, but the real objects in 0.81.0 have start and end (the example prints them).

  4. 4

    Events. addEventListener(type, fn) with ready (WASM started), load, play, pause, stop, complete (a non-looping play reached its end), loop (a looping play wrapped), frame (every drawn frame), loadError and renderError. A looping segment fires loop, never complete. We measured both: idle fired loop repeatedly and a blink at quarter speed fired complete once.

  5. 5

    Bad input is ignored, not thrown. In the errors example: play() before load does nothing; setSpeed(0) and setSpeed(-1) leave the speed at 2; setFrame(500) leaves the frame at 30; setSegment(60, 20) resets the segment to [0, 119]; an unknown marker resets it the same way and clears marker; play() after destroy() does nothing. No exceptions: check state, do not rely on errors.

  6. 6

    **Failures arrive as loadError.** A missing file gives Error: Failed to load animation data from URL: … 404: Not Found and the library also writes it to the console. A file that is not Lottie gives Error: failed to load. A page that sends the wrong thing for the WASM (Topic 16.5) gives another. Always listen for loadError.

  7. 7

    The Motion Timeline and this player. The player draws on a canvas with its own requestAnimationFrame loop, not with CSS or the Web Animations API, so the timeline shows no track for it ((no animations) in our runs). It does follow the timeline's clock: when we froze the clock after pressing play, the frame stayed at 0; stepping to 200, 500, 1000, 1500 and 2500 ms gave frames 0, 5.5, 14.5, 29.5 and 44.5 (complete fired at the 2,500 ms step), then 45, the end of greet, at 3,500 ms. The first frame change came after 200 ms, and the first 500 ms gave only 5.5 frames where 15 would be expected at 30 fps, and an earlier run gave 2.5 there. We did not find why, so do not build exact timing on the first moments of a play. Pause, step and the slow speeds work; the value graph and tracks do not.

  8. 8

    Canvas size. The player copies the canvas's size into its drawing buffer when it starts, and does not follow later CSS changes unless you ask. Set renderConfig: { autoResize: true } or call resize(). renderConfig also holds devicePixelRatio and freezeOnOffscreen (default true: the player stops drawing when the canvas leaves the screen).

  9. 9

    Themes, slots and state machines exist in the API (setTheme, setSlots, stateMachineLoad, with a note that state machines are experimental) but they need a file that contains them. Our headlight has none, so we cannot run them here: setTheme() and stateMachineLoad() return false for it. Topic 16.4 builds the state-machine idea in plain JavaScript instead.

Explain it without notes

01

Why does play() right after new DotLottie(...) do nothing?

02

What is the difference between the loop and complete events?

03

Why does a canvas that CSS makes larger look soft until resize() or autoResize?

Practice

01

Write a function playOnce(player, marker) that plays a marker once and returns a promise that resolves on complete.

02

Write the code to load a file and, on loadError, set an element's text to Mascot unavailable.

Done when you can

  • You can create a player, wait for load, and play a marker once or in a loop

  • You know which events fire for a loop and for a completed play

  • You know which invalid calls are ignored and which failures raise loadError

  • You can say why the timeline shows no track for a canvas player, and what it still controls