Shots in this scene
Loading, Size and Failure States
In one line
A designer-made animation arrives late and can fail. Load the player only when the canvas is near the screen, show a still poster, handle loadError, stop drawing off screen, and give reduced-motion visitors one still frame.
Think of it like this
A shop window with a clockwork display. The shopkeeper winds it only when someone stops to look, shows a painted card while it is being set up, switches it off when no one is near, and keeps the card up if the mechanism breaks. A good animation on a page behaves like that shopkeeper.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- lazy loading
- Fetching a resource only when it is about to be needed, not when the page first loads.
- poster frame
- One still picture that stands in for the animation: before it loads, when it fails, or for reduced motion.
- rootMargin
- An
IntersectionObserversetting that makes an element count as visible a little before it really is. - fallback
- What the page shows when the real thing cannot load. It should take the same space.
- layout shift
- Content jumping because something loaded late and took space. Reserving the size prevents it.
- dynamic import
import(): loads a module when the line runs, as a promise. It rejects if the download fails.
Step by step
01Reserve the space
Give the canvas a fixed width and height in CSS (120px here). Before the file arrives the box is empty but the page does not move later.
02Wait until it is near the screen
An IntersectionObserver fires when the canvas comes within the margin. Only then create the player. The first example proves it by counting the resource requests before and after.
03Draw the poster, then decide
On load: if motion is allowed play the greeting, else setFrame(30) and stop. The second example does the three cases in a row: motion on, motion off, file missing.
04Plan for the WASM failing
Point the player at a WASM file that does not exist (third example). Read the real console lines. The fix is not in the player: it is your loadError handler showing the fallback.
05Measure on a real phone
Our 0.2 ms per frame is a laptop in a test browser. Open your page on the cheapest device you support, with DevTools' network throttling set to a slow connection, and check both the poster and the fallback.
Try it yourself
- 1
Scroll less
In the lazy example change
scrollTo(0, 250)toscrollTo(0, 20). Only the first line prints: the canvas never gets near the visible part of the box, so the player is never created and nothing was downloaded. That is the saving. - 2
Switch on Reduced motion
Under the poster example turn on Reduced motion and press Run. The last line says
motion reduced. The case that the page's own setting decides now matches the second mascot: one still frame.
Code & diagrams
The box scrolls by itself after 300 ms (standing in for the visitor). In this preview the library is imported at the top, because dynamic `import()` has no import map in a page without a static import. The WASM and the file are still fetched only when `new DotLottie` runs; the counts come from `performance.getEntriesByType("resource")`.
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
at the start: player not created, wasm and lottie requests: 0
canvas seen: creating the player now
loaded: poster frame drawn, wasm and lottie requests: 2Three mascots: playing, a still poster frame (the reduced-motion answer), and a plain shape for a file that does not exist. The last line reads this page's own setting: switch on Reduced motion under the preview and run it again to see it say `reduced`, with the first mascot still playing because the example forces each case.
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
motion allowed: playing greet
reduced motion: still poster frame
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
file missing: fallback shape shown
this page's own setting: motion allowed`DotLottie.setWasmUrl()` must run before the first player: it only affects a WASM load that has not started. The `[warn]` and `[error]` lines are the library's own, in order; then your `loadError` handler runs and the page tells the visitor what to expect.
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
`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:
TypeError: Failed to execute 'compile' on 'WebAssembly': HTTP status code is not ok
Primary WASM load failed from /data/anim/nope.wasm: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 6e 6f 74 20 @+0
Retrying WASM load with buffered instantiation
Buffered WASM load from /data/anim/nope.wasm failed: fetch /data/anim/nope.wasm responded with 404 Not Found
Primary WASM URL failed: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 6e 6f 74 20 @+0
Buffered fallback failed: fetch /data/anim/nope.wasm responded with 404 Not Found
[dotlottie-web] Initialization failed: Error: WASM loading failed from all sources.
loadError: Error: Failed to load wasm module: Error: WASM loading failed from all sources.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 `loadError` handler
In the poster example delete the whole p.addEventListener("loadError", …) block.
Myth vs fact
Myth
The poster is only for slow networks.
Fact
It is also the reduced-motion picture and the failure picture. One frame serves all three, so draw it deliberately.
Myth
freezeOnOffscreen makes lazy loading unnecessary.
Fact
It stops drawing. It does not stop the download. The 530 KB still comes at page load unless you create the player late.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The library writes its own failure text to the console (
[error] Failed to load animation data from URL…). Your monitoring (Topic 18.5) can listen toloadErrorto count failures instead of scraping console text. - ▸
Never wait for the animation to show the page. The mascot is decoration: nothing may depend on its
loadorcomplete, and a focus or click handler must work with it absent. - ▸
Preload with care:
<link rel="preload" as="fetch">for a file you will surely need soon can help, but a preloaded file that is never used costs the same bytes as any other.
Remember this
- 1
The problem this solves: a player of about 530 KB over the network (Topic 16.1) loaded for a mascot at the bottom of the page slows everything above it. And any of three things can fail: the file (404 or cut off), the player's WASM, or the visitor's wish for less motion. A page that handles only the happy path leaves a hole or a spinner.
- 2
Load lazily. Watch the canvas with an
IntersectionObserver(arootMarginmakes it count as visible a little early; the build uses 100 px). Only then import the library and create the player. In the example below, before the scroll there were 0 requests for.wasmor.lottiefiles, and after it 2. In your project useawait import("@lottiefiles/dotlottie-web")inside the callback, so the 165 KB of JavaScript waits too; Vite splits it into its own file. - 3
Show something before the player is ready. The canvas is empty until
load. Reserve its size in CSS so the layout does not jump, and give it a background or a poster picture. Afterload, callsetFrame(n)to draw one still frame; this is also the reduced-motion picture. For our mascot frame 30 is the settled pose with the lamp up and the beam on (we looked at it). - 4
Handle every failure with the same fallback.
loadErrorcovers a missing file (404), a file that is not Lottie, and a player WASM that cannot load. In our run a WASM URL that returned 404 produced three[warn]lines, then three[error]lines and finallyloadError: Error: Failed to load wasm module: Error: WASM loading failed from all sources.The fallback (a plain shape, a still image, or nothing) must be the same size so the layout stays put. Wrap the dynamicimport()intry/catchtoo: offline or blocked, it rejects. - 5
Stop when nobody is looking.
renderConfig.freezeOnOffscreenis on by default. Our own observer adds control: it pauses a half-finished greeting when the canvas leaves the screen and resumes it on return. A hidden tab pausesrequestAnimationFrame, which the player's loop uses; we did not test what the mascot does when the tab returns, so the build does not depend on it. - 6
Reduced motion means a still, not nothing. Check the setting after
load: if motion is off, draw the poster frame and do not play. Also listen for changes (onMotionChange, Topic 7.3), because a visitor can switch it while the page is open: the build pauses and shows the poster again. - 7
Measure size and cost. Size:
wc -cfor the file, and the network panel for the player (about 165 KB and 1.24 MB, or 33 KB and 498 KB gzipped, in our package). CPU: we timed 120setFrame()calls for the 200 × 200 mascot and got 0.26, 0.22 and 0.19 ms per frame in three rounds, headless Chromium on this laptop. That is a measurement of drawing one frame, not of a phone: Lena's cheap Android needs its own test.
Explain it without notes
Why can lazy loading matter more for a Lottie than for a GIF?
What does the player report when its WASM fails to load, and what should the page do?
What should a reduced-motion visitor see for a mascot, and why not nothing?
Practice
Write an IntersectionObserver callback that creates the player once, when the canvas is near the screen, and then stops observing.
Write the loadError handler that replaces the canvas by a <div class="fallback"> of the same size.
Done when you can
You can create a player only when its canvas is near the screen
You can draw a poster frame and use it for load, failure and reduced motion
You handle
loadErrorand a failingimport()with the same fallbackYou can measure the file, the player and the per-frame cost, and say where the measurement was made