Shots in this scene
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.
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
widthandheightattributes), 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
Watch loop and complete
In the player example press idle (loops) and wait a few seconds: the console gets
event: loopeach time the 2-second segment wraps and neverevent: complete. Press blink andcompleteappears once. - 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
completearrived 2,005 ms afterplayat 0.25× (real time), and 518 ms after at normal speed. Pause with the timeline and step to look at the frames. - 3
Try an invalid speed
In the errors example change
setSpeed(2)tosetSpeed(0.5): the printed speed becomes 0.5 becausesetSpeed(0)andsetSpeed(-1)after it are ignored.
Code & diagrams
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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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 1The `[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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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 falseBoth 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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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: 1Break 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.
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.freezeOnOffscreenistrueby default, so the player already stops when its canvas scrolls away. mascot.js (Topic 16.6) adds its ownIntersectionObserveras well, so it can also pause a half-played greeting and resume it, and so the player itself loads late. - ▸
DotLottieWorkerexists 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.tssays{ 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
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
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 theloadevent. - 3
Play pieces.
setMarker("greet")makes a marker the active segment.setSegment(10, 20)sets frames directly.play(),pause()(keeps the frame) andstop()(back to the start) control it,setFrame(n)shows one frame, andsetSpeed(x),setLoop(true)andsetMode("bounce")shape playback.markers()returns objects withname,startandend; the installed type file saystimeandduration, but the real objects in 0.81.0 havestartandend(the example prints them). - 4
Events.
addEventListener(type, fn)withready(WASM started),load,play,pause,stop,complete(a non-looping play reached its end),loop(a looping play wrapped),frame(every drawn frame),loadErrorandrenderError. A looping segment firesloop, nevercomplete. We measured both:idlefiredlooprepeatedly and ablinkat quarter speed firedcompleteonce. - 5
Bad input is ignored, not thrown. In the errors example:
play()beforeloaddoes nothing;setSpeed(0)andsetSpeed(-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 clearsmarker;play()afterdestroy()does nothing. No exceptions: check state, do not rely on errors. - 6
**Failures arrive as
loadError.** A missing file givesError: Failed to load animation data from URL: … 404: Not Foundand the library also writes it to the console. A file that is not Lottie givesError: failed to load. A page that sends the wrong thing for the WASM (Topic 16.5) gives another. Always listen forloadError. - 7
The Motion Timeline and this player. The player draws on a canvas with its own
requestAnimationFrameloop, 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 (completefired at the 2,500 ms step), then 45, the end ofgreet, 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
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 callresize().renderConfigalso holdsdevicePixelRatioandfreezeOnOffscreen(defaulttrue: the player stops drawing when the canvas leaves the screen). - 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()andstateMachineLoad()returnfalsefor it. Topic 16.4 builds the state-machine idea in plain JavaScript instead.
Explain it without notes
Why does play() right after new DotLottie(...) do nothing?
What is the difference between the loop and complete events?
Why does a canvas that CSS makes larger look soft until resize() or autoResize?
Practice
Write a function playOnce(player, marker) that plays a marker once and returns a promise that resolves on complete.
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 loopYou know which events fire for a loop and for a completed play
You know which invalid calls are ignored and which failures raise
loadErrorYou can say why the timeline shows no track for a canvas player, and what it still controls