lottie-web
Browser player for animations exported from Adobe After Effects with the Bodymovin extension. It reads Lottie JSON plus optional image assets and renders the composition through SVG, Canvas, or HTML. The returned animation object controls playback, speed, direction, segments, frame seeking, events, text updates, and cleanup. This package is the low-level web runtime, not a React component, animation editor, or general replacement for CSS and Web Animations.
Still the direct choice for playing existing After Effects Lottie files on the web. Do not add it for minor interface motion, and test the real exported composition for payload, unsupported features, accessibility, and renderer cost before shipping.
Use it if
- A motion designer already delivers Bodymovin-compatible Lottie JSON and you need faithful playback in a browser
- You need runtime controls such as frame seeking, speed, direction, named segments, or completion events
- You can choose SVG for DOM accessibility and scaling or Canvas for scenes where a large SVG tree performs poorly
- You want a framework-neutral player and are comfortable managing its lifecycle directly
- Your animation is simple enough for CSS or the Web Animations API: lottie-web version 5.13.0 adds about 76.8 KB gzipped before the animation JSON and image assets
- Your composition depends on video, audio, image sequences, or negative layer stretching: the README lists all of those as unsupported
- You expect every After Effects feature to export correctly: masks can carry a large performance cost, expressions have limited support, and complex scenes need testing in the actual browser renderer
- You need a maintained React lifecycle wrapper: lottie-web exposes imperative browser objects, so React code must create, update, and destroy instances itself or use a separate wrapper
- You need fast maintenance response on old browser or renderer edge cases: the repository's last recorded push was September 1, 2025 and its GitHub metadata shows a large combined backlog of issues and pull requests
Setup reality
npm install lottie-web includes TypeScript declarations and has no runtime dependencies or peer dependencies, but that is only the player. Someone still needs Adobe After Effects plus Bodymovin to produce valid JSON, and image or Illustrator layers may create a separate assets directory that must be deployed beside the JSON or redirected with assetsPath. Choose either path or animationData, never both. A fetched path introduces CORS, caching, and deployment-base concerns; bundled animationData increases JavaScript payload. The player measured about 76.8 KB gzipped, before animation JSON and images. SVG is the usual starting point and can include title and description, but complex files create many DOM nodes. Canvas avoids that tree but requires you to handle accessibility, and passing an existing 2D context with clearCanvas false makes clearing your responsibility. Reusing animationData that contains repeaters requires a deep clone before each load because the player mutates it. Safari pages with a base tag can lose SVG masks until setLocationHref(location.href) is called before loading. Framework components must wait for a browser DOM, keep the AnimationItem in a ref, remove listeners, and call destroy on unmount or the canvas, timers, and DOM remain. Also honor prefers-reduced-motion; autoplaying decorative motion is not automatically accessible.
Patterns
Load an SVG animation from JSONload-from-url
import lottie from 'lottie-web';
const animation = lottie.loadAnimation({
container: document.querySelector('#animation'),
renderer: 'svg',
loop: true,
autoplay: true,
path: '/animations/success/data.json',
});The JSON and any relative image assets need correct public URLs and CORS headers. Do not also pass animationData.
Load imported animation dataload-bundled-data
import lottie from 'lottie-web';
import success from './success.json';
const animation = lottie.loadAnimation({
container,
renderer: 'svg',
autoplay: false,
animationData: structuredClone(success),
});Deep-clone reused data when the composition contains repeaters because loadAnimation can mutate the object.
Play, pause, and change speedcontrol-playback
animation.play();
animation.setSpeed(1.5);
animation.pause();
animation.setDirection(-1);
animation.play();Direction must be 1 or -1. These instance methods target one animation, unlike global lottie methods.
Seek to an exact frameseek-to-frame
animation.goToAndStop(48, true);
const totalFrames = animation.getDuration(true);
const seconds = animation.getDuration(false);The second argument true means frame units; false or omission treats the position as time.
Play a named frame rangeplay-segment
animation.playSegments([30, 72], true);forceFlag true interrupts the current segment immediately; false queues the segment until current playback completes.
React to playback completionlisten-for-completion
const onComplete = () => showNextStep();
animation.addEventListener('complete', onComplete);
// during cleanup
animation.removeEventListener('complete', onComplete);A looping animation does not reach a final complete event in the same way; use loopComplete for loop boundaries.
Wait until SVG elements existwait-for-dom
animation.addEventListener('DOMLoaded', () => {
animation.goToAndStop(0, true);
document.querySelector('#animation').classList.add('ready');
});data_ready means animation data loaded; DOMLoaded is the safer event when code needs generated SVG elements.
Give an SVG animation a title and descriptionadd-svg-accessibility
lottie.loadAnimation({
container,
renderer: 'svg',
animationData,
rendererSettings: {
title: 'Payment confirmed',
description: 'A check mark appears inside a circle',
},
});Decorative animation should usually be hidden from assistive technology instead; meaningful animation needs surrounding text too.
Avoid autoplay for reduced motionrespect-reduced-motion
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
const animation = lottie.loadAnimation({
container,
renderer: 'svg',
loop: !reduceMotion,
autoplay: !reduceMotion,
animationData,
});
if (reduceMotion) animation.goToAndStop(animation.totalFrames - 1, true);The player does not apply reduced-motion policy automatically; your application decides the static fallback.
Use the Canvas rendererrender-to-canvas
const animation = lottie.loadAnimation({
container,
renderer: 'canvas',
animationData,
rendererSettings: {
clearCanvas: true,
preserveAspectRatio: 'xMidYMid meet',
dpr: Math.min(devicePixelRatio, 2),
},
});Canvas does not expose an accessible SVG tree; provide an accessible label or equivalent content outside it.
Set the SVG reference base in Safarifix-safari-masks
import lottie from 'lottie-web';
lottie.setLocationHref(window.location.href);
const animation = lottie.loadAnimation(config);Call this before creating animations when a base tag causes Safari SVG masks to disappear.
Release an animation during teardowndestroy-animation
const removeComplete = animation.addEventListener('complete', onComplete);
return () => {
removeComplete?.();
animation.destroy();
};destroy empties the container. In frameworks, call it on unmount so animation frames, listeners, and generated DOM do not leak.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @lottiefiles/dotlottie-web | npm | You receive compressed .lottie containers and want the current LottieFiles web runtime |
| @rive-app/canvas | npm | You need state-machine-driven interactive animation designed in Rive rather than After Effects playback |
| lottie-react | npm | You want a React component wrapper and hooks around Lottie animations instead of managing the imperative player |