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.

Last updated: July 24, 2026 at 2:45 AMEdit this page