npx skills add ...
npx skills add freshtechbro/claudedesignskills --skill lottie-animations
npx skills add freshtechbro/claudedesignskills --skill lottie-animations
After Effects animation rendering for web and React applications. Use this skill when implementing Lottie animations, JSON vector animations, interactive animated icons, micro-interactions, or loading animations. Triggers on tasks involving Lottie, lottie-web, lottie-react, dotLottie, After Effects JSON export, bodymovin, animated SVG alternatives, or designer-created animations. Complements GSAP ScrollTrigger and Framer Motion for scroll-driven and interactive animations.
Lottie is a library for rendering After Effects animations in real-time on web, iOS, Android, and React Native. Created by Airbnb, it allows designers to ship animations as easily as shipping static assets. Animations are exported from After Effects as JSON files using the Bodymovin plugin, then rendered natively with minimal performance overhead.
When to use Lottie:
Key advantages:
1. JSON Lottie (.json)
2. dotLottie (.lottie)
lottie-web (original library):
@lottiefiles/dotlottie-web (modern, recommended):
@lottiefiles/dotlottie-react (React integration):
lottie-react (alternative React wrapper):
1. LottieFiles (lottie.host)
2. Local JSON/dotLottie files
3. After Effects export
1. Export Settings in After Effects:
2. Compression:
3. Lazy Loading:
1. Renderer Selection:
2. Web Workers:
3. Mobile Optimization:
Problem: Not destroying Lottie instances when components unmount.
Solution:
Problem: Event listeners not removed, causing multiple handlers.
Solution:
Problem: Exported JSON files are 500KB+ for simple animations.
Solutions:
Problem: Animation stutters or drops frames.
Solutions:
DotLottieWorker for web worker renderingProblem: Animation doesn't load due to CORS or incorrect paths.
Solution:
Problem: Some After Effects features don't export to Lottie.
Unsupported features:
Solution:
This skill includes:
generate_lottie_component.py - Generate React/Vue/Svelte Lottie component boilerplateoptimize_lottie.py - Optimize Lottie JSON file sizeapi_reference.md - Complete API documentation for lottie-web, lottie-react, and dotlottie-webafter_effects_export.md - Guide for exporting animations from After Effectsperformance_guide.md - Detailed performance optimization strategiesstarter_lottie/ - Complete React + Vite starter template with Lottie examplesexamples/ - Real-world Lottie animation patterns and use casesimport { DotLottie } from '@lottiefiles/dotlottie-web';
new DotLottie({
canvas: document.getElementById('canvas'),
src: 'animation.lottie',
autoplay: true,
loop: true
});import { DotLottieReact } from '@lottiefiles/dotlottie-react';
<DotLottieReact
src="animation.lottie"
loop
autoplay
style={{ height: 300 }}
/>import Lottie from 'lottie-react';
import animationData from './animation.json';
<Lottie animationData={animationData} loop={true} /><!DOCTYPE html>
<html>
<head>
<style>
#canvas {
width: 400px;
height: 400px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="module">
import { DotLottie } from 'https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web/+esm';
new DotLottie({
canvas: document.getElementById('canvas'),
src: 'https://lottie.host/4db68bbd-31f6-4cd8-84eb-189de081159a/IGmMCqhzpt.lottie',
autoplay: true,
loop: true
});
</script>
</body>
</html>import React from 'react';
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
const AnimatedButton = () => {
const [dotLottie, setDotLottie] = React.useState(null);
const handlePlay = () => dotLottie?.play();
const handlePause = () => dotLottie?.pause();
const handleStop = () => dotLottie?.stop();
const handleSeek = (frame) => dotLottie?.setFrame(frame);
return (
<div>
<DotLottieReact
src="button-animation.lottie"
loop
autoplay={false}
dotLottieRefCallback={setDotLottie}
style={{ height: 200 }}
/>
<div>
<button onClick={handlePlay}>Play</button>
<button onClick={handlePause}>Pause</button>
<button onClick={handleStop}>Stop</button>
<button onClick={() => handleSeek(30)}>Seek to frame 30</button>
</div>
</div>
);
};import React, { useEffect } from 'react';
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
const EventDrivenAnimation = () => {
const [dotLottie, setDotLottie] = React.useState(null);
useEffect(() => {
if (!dotLottie) return;
const onLoad = () => console.log('Animation loaded');
const onPlay = () => console.log('Animation started');
const onPause = () => console.log('Animation paused');
const onComplete = () => console.log('Animation completed');
const onFrame = ({ currentFrame }) => console.log('Frame:', currentFrame);
dotLottie.addEventListener('load', onLoad);
dotLottie.addEventListener('play', onPlay);
dotLottie.addEventListener('pause', onPause);
dotLottie.addEventListener('complete', onComplete);
dotLottie.addEventListener('frame', onFrame);
return () => {
dotLottie.removeEventListener('load', onLoad);
dotLottie.removeEventListener('play', onPlay);
dotLottie.removeEventListener('pause', onPause);
dotLottie.removeEventListener('complete', onComplete);
dotLottie.removeEventListener('frame', onFrame);
};
}, [dotLottie]);
return (
<DotLottieReact
src="animation.lottie"
loop
autoplay
dotLottieRefCallback={setDotLottie}
/>
);
};import Lottie from 'lottie-react';
import robotAnimation from './robot.json';
const ScrollAnimation = () => {
const interactivity = {
mode: 'scroll',
actions: [
{
visibility: [0, 0.2],
type: 'stop',
frames: [0]
},
{
visibility: [0.2, 0.45],
type: 'seek',
frames: [0, 45]
},
{
visibility: [0.45, 1.0],
type: 'loop',
frames: [45, 60]
}
]
};
return (
<Lottie
animationData={robotAnimation}
style={{ height: 300 }}
interactivity={interactivity}
/>
);
};import { useLottie, useLottieInteractivity } from 'lottie-react';
import likeButton from './like-button.json';
const HoverAnimation = () => {
const lottieObj = useLottie({
animationData: likeButton
});
const Animation = useLottieInteractivity({
lottieObj,
mode: 'cursor',
actions: [
{
position: { x: [0, 1], y: [0, 1] },
type: 'loop',
frames: [45, 60]
},
{
position: { x: -1, y: -1 },
type: 'stop',
frames: [45]
}
]
});
return <div style={{ height: 300, border: '2px solid black' }}>{Animation}</div>;
};import { DotLottieReact } from '@lottiefiles/dotlottie-react';
import React, { useState, useEffect } from 'react';
const ThemedAnimation = () => {
const [dotLottie, setDotLottie] = useState(null);
const [animations, setAnimations] = useState([]);
const [themes, setThemes] = useState([]);
const [currentAnimationId, setCurrentAnimationId] = useState('');
const [currentThemeId, setCurrentThemeId] = useState('');
useEffect(() => {
if (!dotLottie) return;
const onLoad = () => {
setAnimations(dotLottie.manifest.animations || []);
setThemes(dotLottie.manifest.themes || []);
setCurrentAnimationId(dotLottie.activeAnimationId);
setCurrentThemeId(dotLottie.activeThemeId);
};
dotLottie.addEventListener('load', onLoad);
return () => dotLottie.removeEventListener('load', onLoad);
}, [dotLottie]);
return (
<div>
<DotLottieReact
src="multi-animation.lottie"
dotLottieRefCallback={setDotLottie}
animationId={currentAnimationId}
themeId={currentThemeId}
/>
{themes.length > 0 && (
<select value={currentThemeId} onChange={(e) => setCurrentThemeId(e.target.value)}>
{themes.map((theme) => (
<option key={theme.id} value={theme.id}>{theme.id}</option>
))}
</select>
)}
{animations.length > 0 && (
<select value={currentAnimationId} onChange={(e) => setCurrentAnimationId(e.target.value)}>
{animations.map((anim) => (
<option key={anim.id} value={anim.id}>{anim.id}</option>
))}
</select>
)}
</div>
);
};import { DotLottieWorker } from '@lottiefiles/dotlottie-web';
// Offload animation rendering to a web worker
new DotLottieWorker({
canvas: document.getElementById('canvas'),
src: 'heavy-animation.lottie',
autoplay: true,
loop: true,
workerId: 'worker-1' // Group multiple animations by worker
});
// Multiple animations in separate workers
new DotLottieWorker({
canvas: document.getElementById('canvas-2'),
src: 'animation-2.lottie',
autoplay: true,
loop: true,
workerId: 'worker-2'
});import Lottie from 'lottie-react';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
import animationData from './animation.json';
gsap.registerPlugin(ScrollTrigger);
const GSAPLottieIntegration = () => {
const lottieRef = React.useRef();
React.useEffect(() => {
const anim = lottieRef.current;
if (!anim) return;
// Sync Lottie with scroll
gsap.to(anim, {
scrollTrigger: {
trigger: '#animation-section',
start: 'top center',
end: 'bottom center',
scrub: 1,
onUpdate: (self) => {
const frame = Math.floor(self.progress * (anim.totalFrames - 1));
anim.goToAndStop(frame, true);
}
}
});
}, []);
return (
<div id="animation-section" style={{ height: '200vh' }}>
<Lottie
lottieRef={lottieRef}
animationData={animationData}
autoplay={false}
loop={false}
/>
</div>
);
};import { motion } from 'framer-motion';
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
const MotionLottie = () => {
return (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6 }}
>
<DotLottieReact
src="animation.lottie"
loop
autoplay
style={{ height: 400 }}
/>
</motion.div>
);
};<script setup>
import { DotLottieVue } from '@lottiefiles/dotlottie-vue';
</script>
<template>
<DotLottieVue
style="height: 500px; width: 500px"
autoplay
loop
src="https://path-to-animation.lottie"
/>
</template><script lang="ts">
import { DotLottieSvelte } from '@lottiefiles/dotlottie-svelte';
import type { DotLottie } from '@lottiefiles/dotlottie-svelte';
let dotLottie: DotLottie | null = null;
function play() {
dotLottie?.play();
}
</script>
<DotLottieSvelte
src="animation.lottie"
loop={true}
autoplay={true}
dotLottieRefCallback={(ref) => dotLottie = ref}
/>
<button on:click={play}>Play</button>const LazyLottie = () => {
const [shouldLoad, setShouldLoad] = React.useState(false);
React.useEffect(() => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setShouldLoad(true);
}
});
observer.observe(document.getElementById('lottie-trigger'));
return () => observer.disconnect();
}, []);
return (
<div id="lottie-trigger">
{shouldLoad && <DotLottieReact src="animation.lottie" loop autoplay />}
</div>
);
};// SVG: Best quality, slower for complex animations
// Canvas: Better performance, rasterized
// HTML: Limited support, use only for simple animations
// For complex animations, prefer canvas
new DotLottie({
canvas: document.getElementById('canvas'),
src: 'animation.lottie',
autoplay: true,
loop: true,
renderConfig: {
devicePixelRatio: window.devicePixelRatio || 1
}
});// Offload to worker for heavy animations
import { DotLottieWorker } from '@lottiefiles/dotlottie-web';
new DotLottieWorker({
canvas: document.getElementById('canvas'),
src: 'heavy-animation.lottie',
autoplay: true,
loop: true
});// Reduce quality on mobile
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
new DotLottie({
canvas: document.getElementById('canvas'),
src: isMobile ? 'animation-low.lottie' : 'animation-high.lottie',
autoplay: true,
loop: true,
renderConfig: {
devicePixelRatio: isMobile ? 1 : window.devicePixelRatio
}
});const SafeAnimation = () => {
const [dotLottie, setDotLottie] = React.useState(null);
React.useEffect(() => {
return () => {
// Always destroy instance on unmount
dotLottie?.destroy();
};
}, [dotLottie]);
return <DotLottieReact src="animation.lottie" dotLottieRefCallback={setDotLottie} />;
};useEffect(() => {
if (!dotLottie) return;
const handleComplete = () => console.log('Complete');
dotLottie.addEventListener('complete', handleComplete);
// MUST return cleanup function
return () => {
dotLottie.removeEventListener('complete', handleComplete);
};
}, [dotLottie]);// Use animationData for local imports (best for bundled apps)
import animationData from './animation.json';
<Lottie animationData={animationData} />
// OR use path for external URLs (requires CORS headers)
<DotLottieReact src="https://example.com/animation.lottie" />
// For Next.js, place in public/ folder
<DotLottieReact src="/animations/animation.lottie" />