Loading...
Loading...
Expert guidelines for implementing performant Lottie animations on the web
npx skill4agent add mindrally/skills lottie.lottie.json# For React
npm install @lottiefiles/dotlottie-react
# For vanilla JS
npm install @lottiefiles/dotlottie-webimport { DotLottieReact } from "@lottiefiles/dotlottie-react";
function Animation() {
return (
<DotLottieReact
src="/animations/loading.lottie"
loop
autoplay
/>
);
}import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { useState } from "react";
function ControlledAnimation() {
const [dotLottie, setDotLottie] = useState(null);
const dotLottieRefCallback = (dotLottie) => {
setDotLottie(dotLottie);
};
return (
<>
<DotLottieReact
src="/animation.lottie"
dotLottieRefCallback={dotLottieRefCallback}
/>
<button onClick={() => dotLottie?.play()}>Play</button>
<button onClick={() => dotLottie?.pause()}>Pause</button>
<button onClick={() => dotLottie?.stop()}>Stop</button>
</>
);
}import { useEffect, useRef, useState } from "react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
function LazyLottie({ src }) {
const [isVisible, setIsVisible] = useState(false);
const containerRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ rootMargin: "100px" }
);
if (containerRef.current) {
observer.observe(containerRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={containerRef}>
{isVisible && <DotLottieReact src={src} autoplay loop />}
</div>
);
}// SVG renderer - best quality, good for simple animations
<DotLottieReact src="/animation.lottie" renderer="svg" />
// Canvas renderer - better performance for complex animations
<DotLottieReact src="/animation.lottie" renderer="canvas" />
// Use canvas for:
// - Complex animations with many elements
// - Lower-powered devices
// - Animations with filters/effectsAVOID:
- Masks (use alpha matte sparingly)
- Complex blur effects
- 3D layers
- Expressions
- Uncompressed images
- Large image assets
PREFER:
- Simple shapes (fills, strokes)
- Transform animations (position, scale, rotation)
- Opacity changes
- Path animations- Compress images to match display size
- If max display is 400x400, don't use 1000x1000 images
- Use vector graphics when possible
- Consider converting images to shapes<DotLottieReact
src="/hover-animation.lottie"
playMode="hover"
/>import { useScroll, useTransform } from "motion/react";
function ScrollLottie() {
const { scrollYProgress } = useScroll();
const [dotLottie, setDotLottie] = useState(null);
useEffect(() => {
if (!dotLottie) return;
const unsubscribe = scrollYProgress.on("change", (progress) => {
dotLottie.setFrame(progress * dotLottie.totalFrames);
});
return unsubscribe;
}, [dotLottie, scrollYProgress]);
return (
<DotLottieReact
src="/scroll-animation.lottie"
dotLottieRefCallback={setDotLottie}
autoplay={false}
/>
);
}function SegmentAnimation() {
const [dotLottie, setDotLottie] = useState(null);
const playSegment = (start, end) => {
dotLottie?.setSegment(start, end);
dotLottie?.play();
};
return (
<>
<DotLottieReact
src="/multi-state.lottie"
dotLottieRefCallback={setDotLottie}
autoplay={false}
/>
<button onClick={() => playSegment(0, 30)}>State 1</button>
<button onClick={() => playSegment(30, 60)}>State 2</button>
</>
);
}function AccessibleAnimation() {
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
if (prefersReducedMotion) {
return <img src="/static-fallback.svg" alt="Animation description" />;
}
return (
<DotLottieReact
src="/animation.lottie"
autoplay
loop
aria-label="Decorative loading animation"
/>
);
}function AnimationWithFallback() {
const [hasError, setHasError] = useState(false);
if (hasError) {
return <img src="/fallback.gif" alt="Animation" />;
}
return (
<DotLottieReact
src="/animation.lottie"
autoplay
onError={() => setHasError(true)}
/>
);
}function AnimationWithPreloader() {
const [isLoaded, setIsLoaded] = useState(false);
return (
<div className="animation-container">
{!isLoaded && (
<img src="/first-frame.webp" alt="" className="preloader" />
)}
<DotLottieReact
src="/large-animation.lottie"
onLoad={() => setIsLoaded(true)}
style={{ opacity: isLoaded ? 1 : 0 }}
autoplay
/>
</div>
);
}| Animation Complexity | Target Size | Max DOM Elements |
|---|---|---|
| Simple icons | < 10KB | < 100 |
| UI animations | < 50KB | < 500 |
| Complex scenes | < 150KB | < 1500 |
| Hero animations | < 300KB | < 2500 |
useEffect(() => {
return () => {
dotLottie?.destroy();
};
}, [dotLottie]);