Lottie animations are efficient by default â but they can still tank performance if you're not careful. This guide covers every optimization technique: renderer selection, off-screen pausing, file size reduction, memory management, and debugging.
Why Performance Matters for Lottie
A single Lottie animation running off-screen wastes CPU/GPU. Ten simultaneous animations can cause frame drops. A 500KB JSON file delays LCP. These problems compound on mobile, where batteries drain and memory is limited.
The good news: most Lottie performance issues are fixable in under 10 minutes.
Step 0: Audit Your Files First
Before optimizing code, audit the animation files themselves. Open each .json or .lottie file in IconKing:
- Check file size and layer count
- Identify unsupported effects (Gaussian blur, 3D layers) that force fallback rendering
- Convert
.jsonâ.lottiefor 75% file size reduction - Verify the animation renders correctly before adding it to your build
Small file â less parsing time â faster first paint.
1. Choose the Right Renderer
Lottie supports three renderers: SVG, Canvas, and HTML. The choice has a major performance impact.
SVG (default)
- Creates DOM nodes for every animated element
- Best quality, highest compatibility
- Use for: 1â5 simultaneous animations, complex paths, sharp edges at all sizes
Canvas
- Renders to a single bitmap â no DOM overhead
- Significantly faster for many simultaneous animations
- Use for: 6+ simultaneous animations, lists/grids of animated items, game-like UIs
HTML
- CSS-based, very limited feature support
- Fastest for simple animations, but most Lottie files break
- Use for: Only the simplest animations with CSS-compatible properties
// Switch to Canvas for performance-critical scenarios
const anim = lottie.loadAnimation({
container: el,
renderer: 'canvas', // â change this
loop: true,
autoplay: true,
path: '/animation.json'
});
Rule of thumb: Use SVG until you notice frame drops. Switch to Canvas if rendering 5+ animations simultaneously.
2. Pause Off-Screen Animations (IntersectionObserver)
Running animations consume CPU even when invisible. Always use IntersectionObserver to pause/play based on visibility:
const anim = lottie.loadAnimation({ /* ... */ });
const observer = new IntersectionObserver(([entry]) => {
entry.isIntersecting ? anim.play() : anim.pause();
}, { threshold: 0.1 });
observer.observe(document.getElementById('anim-container'));
React:
import { useRef, useEffect } from 'react';
import Lottie, { LottieRefCurrentProps } from 'lottie-react';
export function IntersectionLottie({ animationData }) {
const containerRef = useRef(null);
const lottieRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
entry.isIntersecting ? lottieRef.current?.play() : lottieRef.current?.pause();
}, { threshold: 0.1 });
if (containerRef.current) observer.observe(containerRef.current);
return () => observer.disconnect();
}, []);
return (
<div ref={containerRef}>
<Lottie lottieRef={lottieRef} animationData={animationData} autoplay={false} />
</div>
);
}
Impact: On a page with 8 animations in a long scroll, pausing off-screen ones reduces CPU usage by 60â80%.
3. Use .lottie Format (75% Smaller Files)
The .lottie (dotLottie) format compresses animations significantly over plain JSON:
| Format | Typical Size | Load Time (3G) |
|---|---|---|
.json |
80KB | 320ms |
.lottie |
20KB | 80ms |
Convert at IconKing â drop the file, click convert, download. Then switch to @lottiefiles/dotlottie-web:
npm install @lottiefiles/dotlottie-web
# or React
npm install @lottiefiles/dotlottie-react
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
<DotLottieReact
src="/animations/loading.lottie"
loop
autoplay
/>
Impact: Faster LCP, better Core Web Vitals Lighthouse scores, less bandwidth on mobile.
4. Lazy Load Animation Files
Don't bundle large animation files with your JavaScript. Load them on demand:
// Bad: bundled in JS
import animData from './heavy-animation.json'; // added to main bundle
// Good: loaded at runtime
async function loadAnim(container) {
const response = await fetch('/animations/heavy.json');
const animData = await response.json();
return lottie.loadAnimation({ container, animationData: animData, renderer: 'svg', loop: true, autoplay: true });
}
React with Next.js dynamic import:
import dynamic from 'next/dynamic';
const HeavyAnimation = dynamic(
() => import('@/components/HeavyAnimation'),
{
ssr: false,
loading: () => <div className="w-48 h-48 bg-gray-100 animate-pulse rounded-lg" />
}
);
Impact: Reduces initial JS bundle size. Critical for First Contentful Paint.
5. Preload Critical Above-the-Fold Animations
For hero animations that appear immediately, preload them:
<link rel="preload" href="/animations/hero.lottie" as="fetch" crossorigin>
Or in Next.js <Head>:
import Head from 'next/head';
<Head>
<link rel="preload" href="/animations/hero.lottie" as="fetch" crossOrigin="anonymous" />
</Head>
Impact: Eliminates the flash of a missing animation on initial load.
6. Limit Simultaneous Animations
Each running animation runs its own requestAnimationFrame loop. On mobile:
- 1â3 animations: negligible impact
- 4â8 animations: noticeable battery drain
- 10+ animations: frame drops likely
Strategies:
- Stagger animation starts so not all fire at once
- Use
lottie.pause()to pause all animations when the tab is hidden - Use IntersectionObserver aggressively â only play what's visible
// Pause all when tab hidden
document.addEventListener('visibilitychange', () => {
document.hidden ? lottie.pause() : lottie.play();
});
7. Reduce Canvas Size
A 1000Ã1000 Canvas animation rendered in a 50Ã50 box wastes GPU. Set the container to the actual display size:
const anim = lottie.loadAnimation({
container: el,
renderer: 'canvas',
rendererSettings: {
clearCanvas: true,
// Don't render at 2x unnecessarily
dpr: 1 // override devicePixelRatio if retina isn't needed
}
});
For SVG renderer, don't scale a 400Ã400 animation down to 24Ã24 via CSS â export a smaller version from After Effects.
8. Destroy Animations You No Longer Need
Idle Lottie instances consume memory. Always destroy:
// Vanilla JS
anim.destroy();
// React â cleanup in useEffect
useEffect(() => {
const anim = lottie.loadAnimation({ /* ... */ });
return () => anim.destroy();
}, []);
In SPAs (React, Vue, Angular), this is critical. Route changes don't automatically clean up DOM-based animations.
9. Use Progressive Loading
For complex animations, use progressiveLoad: true to start rendering before the full file has parsed:
lottie.loadAnimation({
container: el,
renderer: 'svg',
rendererSettings: {
progressiveLoad: true
},
path: '/animations/complex.json'
});
Impact: Animation appears sooner, even for large files.
10. Simplify Complex Animations at the Source
If an animation is still slow after all of the above, the issue is in the After Effects file itself:
- Too many layers: Ask your designer to merge/flatten layers
- Unsupported effects: Gaussian blur, drop shadows, certain blend modes force CPU fallback
- Raster images embedded: Dramatic file size increase; use vector shapes instead
- Very high frame rate: 60fps animations are 2Ã larger than 30fps; request 24â30fps for most use cases
Open the file in IconKing to inspect layer count and check for rendering issues before sending the designer a revision request.
Quick Audit Checklist
| Check | Action |
|---|---|
File format is .json
|
Convert to .lottie at IconKing |
| Animation runs off-screen | Add IntersectionObserver pause/play |
| Many simultaneous animations | Switch to Canvas renderer |
| Animation in SPA | Add destroy() in component cleanup |
| Hero animation delays LCP | Add <link rel="preload">
|
| File size > 100KB | Simplify at source or split animation |
| Tab hidden = animation running | Add visibilitychange listener |
Measuring Performance
Use Chrome DevTools Performance tab to profile:
- Record a 5-second window with animations running
- Look for long paint/render tasks in the main thread
- Check GPU layers â excessive layer promotion indicates over-animation
For Core Web Vitals, Lighthouse will flag LCP issues from large animation files.
Summary
-
File format: Convert to
.lottieat IconKing for 75% smaller files - Renderer: SVG for quality, Canvas for 6+ simultaneous animations
- Visibility: IntersectionObserver pause/play on every animation
- Loading: Lazy load large files; preload critical above-fold ones
- Memory: Destroy animations in component cleanup
-
Tab: Pause all when
document.hidden - Source: Work with designers to reduce layer count and remove unsupported effects
Top comments (0)