Optimize performance
Speed up dotLottie animations on the web - preload the WASM engine, tune render configuration, manage memory, and lazy-load animations.
If animations start slowly, drop frames, or consume too much CPU or memory, work through the techniques below. Each one is independent — apply the ones that match your bottleneck.
Preload the WASM engine
The player's WASM engine (~500 KB compressed) is fetched from a CDN the first time a player is constructed. This download sits on the critical path of your first animation — the animation won't render until the WASM is ready.
Call DotLottie.preload() at app or route load time, before any player is constructed, to start the download early:
import { DotLottie } from "@lottiefiles/dotlottie-web";
// At app or route load, before any player is constructed:
DotLottie.preload();preload() returns a Promise<void> that resolves when the WASM is compiled and ready. You can await it if you need to know exactly when the engine is available, but you don't need to — the player will use the already-in-progress download automatically.
For even earlier loading, add a <link rel="preload"> tag to your HTML (crossorigin is required — without it the preloaded response cannot be reused and the file downloads twice):
<link rel="preconnect" href="https://cdn.jsdelivr.net" />
<link
rel="preload"
as="fetch"
crossorigin
href="https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@0.78.0/dist/dotlottie-player.wasm"
/>:::warning
The version in the <link> URL must exactly match your installed package version. The player fetches a version-pinned URL, and a mismatch means the browser cannot reuse the preloaded response.
:::
If you use setWasmUrl() to point to a custom WASM binary, call it before preload() and point the preload tag at the same URL:
import { DotLottie } from "@lottiefiles/dotlottie-web";
DotLottie.setWasmUrl("/assets/dotlottie-player.wasm");
DotLottie.preload();The same preload() method is available on DotLottieWebGL and DotLottieWebGPU for their respective WASM modules.
Tune the render configuration
Lower the device pixel ratio
Rendering cost scales with resolution. Reduce the pixel ratio when full resolution isn't needed:
const dotLottie = new DotLottie({
canvas: document.querySelector("#canvas"),
src: "animation.lottie",
renderConfig: {
// Automatically adjusted for high-DPI displays
devicePixelRatio: window.devicePixelRatio * 0.75,
},
});On high-DPI mobile devices, capping the ratio (for example at 2) keeps output sharp while cutting rendering work.
Disable frame interpolation
Frame interpolation renders subframes for smoother motion at extra cost. Disable it if you don't need subframe accuracy:
const dotLottie = new DotLottie({
canvas: document.querySelector("#canvas"),
src: "animation.lottie",
useFrameInterpolation: false, // Disable for better performance
});Freeze offscreen animations
Stop rendering animations the user can't see:
const dotLottie = new DotLottie({
canvas: document.querySelector("#canvas"),
src: "animation.lottie",
renderConfig: {
freezeOnOffscreen: true,
},
});A combined configuration for constrained devices:
const dotLottie = new DotLottie({
canvas: document.querySelector("#canvas"),
src: "animation.lottie",
renderConfig: {
devicePixelRatio: Math.min(window.devicePixelRatio, 2), // Cap at 2x
freezeOnOffscreen: true,
},
useFrameInterpolation: false, // Disable for high-performance needs
layout: {
fit: "contain", // Use appropriate fit mode
},
});See the render configuration reference for all options, including quality.
Move rendering off the main thread
If complex animations cause high CPU usage or jank on the main thread, render them in a Web Worker with DotLottieWorker:
import { DotLottieWorker } from "@lottiefiles/dotlottie-web";
const animation = new DotLottieWorker({
canvas: document.querySelector("#canvas"),
src: "animation.lottie",
autoplay: true,
workerId: "worker-1",
});See Render animations in a web worker for worker grouping and the Promise-based API.
Manage memory
Destroy players you no longer need and remove event listeners to prevent memory leaks:
// Clean up when animation is no longer needed
dotLottie.destroy();
// Remove event listeners
dotLottie.removeEventListener("frame", frameHandler);
dotLottie.removeEventListener("loop", loopHandler);When working with many animations, keep references so you can clean them all up:
const animations = [];
// Group animations by worker
function createAnimation(src, workerId) {
const animation = new DotLottieWorker({
canvas: document.querySelector(`#canvas-${workerId}`),
src: src,
workerId: workerId,
renderConfig: {
freezeOnOffscreen: true,
devicePixelRatio: window.devicePixelRatio * 0.75,
},
});
animations.push(animation);
}
// Clean up all animations
function cleanup() {
animations.forEach((animation) => animation.destroy());
animations.length = 0;
}Resize efficiently
Enable autoResize, or wrap manual resize calls in requestAnimationFrame:
const dotLottie = new DotLottie({
canvas: document.querySelector("#canvas"),
src: "animation.lottie",
renderConfig: {
autoResize: true, // Automatic canvas resizing
},
});
// Or handle manually if needed
window.addEventListener("resize", () => {
requestAnimationFrame(() => {
dotLottie.resize();
});
});Lazy-load offscreen animations
Load animations only when they scroll into view, and consider preloading critical ones:
// Lazy loading example
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadAnimation(entry.target);
observer.unobserve(entry.target);
}
});
});
function loadAnimation(container) {
const canvas = container.querySelector("canvas");
new DotLottie({
canvas: canvas,
src: canvas.dataset.animation,
autoplay: true,
renderConfig: {
freezeOnOffscreen: true,
},
});
}
// Observe animation containers
document.querySelectorAll(".animation-container").forEach((container) => {
observer.observe(container);
});Monitor frame rate
Measure FPS with the frame event to verify your optimizations:
let lastFrame = performance.now();
let frameCount = 0;
dotLottie.addEventListener("frame", () => {
frameCount++;
const now = performance.now();
if (now - lastFrame >= 1000) {
console.log(`FPS: ${frameCount}`);
frameCount = 0;
lastFrame = now;
}
});Troubleshoot common issues
High CPU usage — use Web Workers for complex animations, implement proper cleanup, and reduce animation complexity.
Memory leaks — destroy animations when not needed, remove event listeners, and clear references to unused objects.
Poor mobile performance — lower the device pixel ratio, disable frame interpolation, and use appropriate canvas sizes.
Slow first frame — call
DotLottie.preload()at app or route load time, and add a<link rel="preload">tag pointing at the WASM file.
Related
How the web player works — why the WASM engine and frame interpolation exist
API Reference for defaults of every render option