Lottie animations can hurt or help your Core Web Vitals depending on how you implement them. Here's what to measure, what to fix, and what's a non-issue.
The Three Risks
Lottie can impact three CWV metrics:
- LCP (Largest Contentful Paint) — if a large Lottie animation is the hero element
- CLS (Cumulative Layout Shift) — if the container doesn't have defined dimensions
- INP (Interaction to Next Paint) — if too many animations are competing for CPU
TL;DR — none of these are inherent to Lottie. They're all implementation problems with known fixes.
LCP: Hero Animations
If your hero section is a Lottie animation, it may be the LCP element.
Problem: .json files are large and parse-heavy. A 150KB Lottie JSON blocks your LCP.
Fix 1: Convert to .lottie format
The same animation as .lottie is typically 30–40KB. Convert at IconKing. This is the single highest-impact change for Lottie performance.
# Before: hero.json = 145KB
# After: hero.lottie = 38KB
Fix 2: Preload the animation file
<link rel="preload" href="/animations/hero.lottie" as="fetch" crossorigin>
Fix 3: Don't make Lottie your LCP element
Place text content as the LCP element, with Lottie as a supporting illustration. A headline renders instantly; a Lottie file doesn't.
CLS: Undefined Container Dimensions
If you don't specify width and height, the container collapses to zero, then jumps to full size when Lottie loads. That's a CLS hit.
Problem:
// CLS: container has no dimensions
<DotLottieReact src="/hero.lottie" loop autoplay />
Fix:
// No CLS: dimensions defined
<DotLottieReact
src="/hero.lottie"
loop
autoplay
style={{ width: 400, height: 400 }}
/>
Or via CSS with aspect-ratio:
.lottie-container {
width: 100%;
aspect-ratio: 1 / 1;
max-width: 400px;
}
Always define dimensions. This is the most common Lottie CLS cause.
INP: Too Many Concurrent Animations
Running 8+ Lottie animations simultaneously — common on product listing pages — blocks the main thread during input handling.
Fix: Intersection Observer
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
const anim = animationMap.get(entry.target);
if (!anim) return;
entry.isIntersecting ? anim.play() : anim.pause();
});
},
{ threshold: 0.1 }
);
document.querySelectorAll('[data-lottie]').forEach(el => {
observer.observe(el);
});
Only run animations that are visible. Pause everything else.
Measurement
Use Lighthouse before and after any Lottie optimization:
npx lighthouse https://yoursite.com --output json | grep -E "lcp|cls|inp"
Or use PageSpeed Insights to compare mobile vs desktop CWV scores.
A well-implemented Lottie animation (correct dimensions, .lottie format, lazy-loaded) should add zero CWV penalty.
Quick Checklist
- [ ] All animations use .lottie format (convert at IconKing)
- [ ] All containers have explicit width and height (no CLS)
- [ ] Hero animation is not the LCP element (or is preloaded)
- [ ] Animations off-screen are paused via IntersectionObserver
- [ ] Max 6–8 animations active simultaneously on mobile
- [ ] Animations destroyed when components unmount
Summary
The .lottie format is the most impactful single change — 75% file size reduction means better LCP and lower parse time. Explicit dimensions eliminate CLS. IntersectionObserver handles INP on animation-heavy pages. All three fixes together typically improve Lighthouse scores by 5–15 points on pages with hero animations.
Top comments (0)