assets/starter_lottie/README.md
# Lottie Starter Template
Complete React + Vite starter template with Lottie examples.
## Features
- React 18+ with TypeScript support
- Vite for fast development
- DotLottie React integration
- Example animations with controls
- Responsive design
- Production-ready configuration
## Installation
```bash
# Install dependencies
npm install
# Development
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
```
## Project Structure
```
starter_lottie/
├── package.json
├── vite.config.js
├── index.html
├── src/
│ ├── main.jsx
│ ├── App.jsx
│ ├── App.css
│ └── components/
│ ├── BasicAnimation.jsx
│ └── InteractiveAnimation.jsx
├── public/
│ └── animations/
│ └── example.lottie
└── README.md
```
## Usage Examples
### Basic Animation
```jsx
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
<DotLottieReact
src="/animations/example.lottie"
loop
autoplay
style={{ height: 400 }}
/>
```
### Interactive Animation
```jsx
const [dotLottie, setDotLottie] = useState(null);
<DotLottieReact
src="/animations/example.lottie"
loop
autoplay={false}
dotLottieRefCallback={setDotLottie}
/>
<button onClick={() => dotLottie?.play()}>Play</button>
```
## Configuration
### vite.config.js
Optimized for production builds with:
- Code splitting
- Asset optimization
- Gzip compression support
### package.json
Includes essential dependencies:
- `@lottiefiles/dotlottie-react`
- `react` and `react-dom`
- Development tools
## Deployment
Build optimized bundle:
```bash
npm run build
```
Output in `dist/` folder ready for deployment to:
- Vercel
- Netlify
- AWS S3 + CloudFront
- Any static hosting
## License
MIT
references/after_effects_export.md
# After Effects to Lottie Export Guide
Complete guide for exporting Lottie animations from After Effects using the Bodymovin plugin.
## Prerequisites
1. **Adobe After Effects** (CC 2015 or later)
2. **Bodymovin Plugin** - Install from:
- **Recommended:** AEsc scripts panel (Extension Manager)
- Manual: https://aescripts.com/bodymovin/
- LottieFiles plugin: https://lottiefiles.com/plugins/after-effects
## Installation
### Method 1: ZXP Installer (Recommended)
1. Download ZXP Installer: https://aescripts.com/learn/zxp-installer/
2. Download Bodymovin ZXP file
3. Drag ZXP file to ZXP Installer
4. Restart After Effects
5. Open Window → Extensions → Bodymovin
### Method 2: Manual Installation
1. Download Bodymovin
2. Extract to:
- **Mac:** `/Applications/Adobe After Effects [version]/Scripts/ScriptUI Panels/`
- **Windows:** `C:\Program Files\Adobe\Adobe After Effects [version]\Support Files\Scripts\ScriptUI Panels\`
3. Restart After Effects
## Export Settings
### Basic Export
1. Open Window → Extensions → Bodymovin
2. Select composition(s) to export
3. Click "Settings" icon (gear)
4. Configure options (see below)
5. Choose destination folder
6. Click "Render"
### Recommended Settings for Web
**Essential:**
- ✅ **Glyphs** - Convert text to shapes (reduces file size)
- ✅ **Hidden** - Skip hidden layers
- ✅ **Skip images that aren't used**
- ✅ **Compress JSON** - Minify output (smaller file)
- ⬜ **Export Mode: Demo** (uncheck for production)
**Advanced:**
- **Export Format:** JSON or dotLottie (.lottie)
- **dotLottie:** Recommended for production (90% smaller)
- **Segments:** Export specific frame ranges
- **Image Quality:** 80% (balance size vs quality)
### File Size Optimization Settings
```
✅ Glyphs (text as shapes)
✅ Compress JSON
✅ Skip images that aren't used
✅ Merge Paths (when possible)
⬜ Pretty Print JSON (development only)
⬜ Include in JSON (embed images - increases size)
```
**Expected File Sizes:**
- Simple icon animation: 5-20 KB
- Complex character animation: 50-150 KB
- Heavy illustration animation: 150-500 KB
- **If >500 KB:** Optimize composition (see below)
## Supported Features
### ✅ Fully Supported
**Shapes:**
- Shape layers (rect, ellipse, polygon, star)
- Paths and bezier curves
- Stroke and fill
- Trim paths
- Merge paths
- Repeaters
**Transforms:**
- Position, scale, rotation, opacity
- Anchor points
- Parenting
**Effects:**
- Stroke (via shape layers)
- Fill (via shape layers)
- Trim paths
- Merge paths
**Masks:**
- Shape masks
- Alpha mattes
- Luma mattes (partial)
**Text:**
- As glyphs (converted to shapes)
- Character animation (requires glyphs)
### ⚠️ Partial Support
**Expressions:**
- Simple expressions work
- Complex expressions may fail
- Test thoroughly after export
**Blending Modes:**
- Limited support
- Normal, Add, Multiply work best
- Others may render incorrectly
**Track Mattes:**
- Alpha mattes: Good support
- Luma mattes: Limited support
- Inverted mattes: May fail
### ❌ Not Supported
**Layer Effects:**
- Drop Shadow → Use shape layer shadow
- Glow → Use shape layer glow
- Bevel & Emboss → Pre-render as image
- All built-in effects → Convert to shapes
**3D Layers:**
- 3D transforms
- Cameras (except 2D camera moves)
- Lights
**Advanced:**
- Adjustment layers
- Time remapping
- Frame blending
- Motion blur (partial)
- Gradient strokes (use solid color)
## Optimization Techniques
### 1. Composition Setup
**Before Animating:**
- Work at 1920x1080 or smaller
- Use round numbers for dimensions
- 30 fps or 60 fps (avoid 23.976, 29.97)
- Keep composition under 5 seconds when possible
**Comp Settings:**
```
Width: 1920px (or target size)
Height: 1080px
Frame Rate: 30 fps
Duration: 2-5 seconds
```
### 2. Shape Layer Optimization
**DO:**
```
✅ Use Shape Layers (not vector layers from AI)
✅ Combine shapes with Merge Paths
✅ Use simple gradients (2-3 colors)
✅ Minimize anchor points (simplify paths)
✅ Use solid fills over gradients when possible
```
**DON'T:**
```
❌ Import AI files directly (convert to shapes)
❌ Use too many shapes (>50 layers = slow)
❌ Animate every property (only what's needed)
❌ Use gradient strokes (solid only)
```
### 3. Image Optimization
**Embedded Images:**
- Resize to actual displayed size
- Use JPG for photos (not PNG)
- Compress before import (TinyPNG)
- Avoid embedding if possible
**Alternative:**
- Use shape layers instead
- Pre-render complex elements
- Load images separately in code
### 4. Text Optimization
**Recommended Approach:**
```
1. Create text layer
2. Enable "Glyphs" in Bodymovin settings
3. Text converts to shapes automatically
```
**Manual Alternative:**
```
1. Select text layer
2. Right-click → Create Shapes from Text
3. Delete original text layer
4. Animate shape layers
```
**Benefits:**
- No font loading required
- Smaller file size
- Guaranteed consistent rendering
### 5. Animation Optimization
**Keyframe Reduction:**
```javascript
// After Effects: Select keyframes
// Right-click → Keyframe Assistant → Reduce Keys
// Threshold: 0.5-1.0 pixels
```
**Simplify Paths:**
```javascript
// Illustrator/After Effects
// Object → Path → Simplify
// Target: 85-90% of original points
```
**Limit Properties:**
```
Only animate:
Position, Scale, Rotation, Opacity
(Most performant)
Avoid animating:
Anchor Point, Shape Path
(More expensive)
```
## Common Export Issues
### Issue 1: Huge File Size (>500 KB)
**Causes:**
- Embedded images
- Too many keyframes
- Complex paths
- Text not converted to glyphs
**Solutions:**
```
1. Enable "Glyphs" setting
2. Enable "Skip images that aren't used"
3. Reduce keyframes (Keyframe Assistant → Reduce Keys)
4. Simplify paths in Illustrator
5. Remove unused layers
6. Export as dotLottie format
```
### Issue 2: Animation Doesn't Match Preview
**Causes:**
- Unsupported features used
- Expression errors
- Blending mode issues
**Solutions:**
```
1. Check Bodymovin console for warnings
2. Replace unsupported features
3. Simplify expressions
4. Use Normal blending mode
5. Test export immediately
```
### Issue 3: Text Rendering Incorrectly
**Causes:**
- Font not supported
- Glyphs not enabled
- Special characters
**Solutions:**
```
1. Enable "Glyphs" in Bodymovin settings
2. OR manually: Create Shapes from Text
3. Avoid special Unicode characters
4. Use web-safe fonts if not using glyphs
```
### Issue 4: Colors Look Different
**Causes:**
- Color space mismatch
- Blending mode issues
- Opacity calculation differences
**Solutions:**
```
1. Work in sRGB color space
2. Use Normal blending mode
3. Check opacity values (0-100%)
4. Avoid nested transparency
```
## Testing Workflow
### 1. Export & Preview
```bash
1. Export from After Effects
2. Preview on LottieFiles:
https://lottiefiles.com/preview
3. Check file size (aim for <100 KB)
4. Test on target devices
```
### 2. Integration Testing
```javascript
// Quick HTML test
<!DOCTYPE html>
<html>
<body>
<div id="lottie"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script>
<script>
lottie.loadAnimation({
container: document.getElementById('lottie'),
renderer: 'svg',
loop: true,
autoplay: true,
path: 'animation.json'
});
</script>
</body>
</html>
```
### 3. Performance Testing
```javascript
// Monitor FPS
const animation = lottie.loadAnimation({...});
let lastTime = performance.now();
let frameCount = 0;
animation.addEventListener('enterFrame', () => {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
console.log('FPS:', frameCount);
frameCount = 0;
lastTime = currentTime;
}
});
// Target: 60 FPS on desktop, 30 FPS on mobile
```
## Best Practices
### Pre-Export Checklist
```
☐ Composition is 1920x1080 or smaller
☐ Frame rate is 30 or 60 fps
☐ Duration is under 10 seconds
☐ All layers are named descriptively
☐ Unused layers are deleted
☐ Text is converted to shapes/glyphs enabled
☐ Images are optimized/removed
☐ No unsupported effects used
☐ Tested in Bodymovin preview
☐ File size is under 100 KB (target)
```
### Export Settings Checklist
```
☐ Format: JSON or dotLottie (.lottie)
☐ Glyphs: Enabled
☐ Hidden: Checked
☐ Skip unused images: Checked
☐ Compress JSON: Enabled
☐ Pretty Print: Disabled (production)
☐ Image Quality: 80%
☐ Export Mode: Standard (not Demo)
```
### Post-Export Checklist
```
☐ Preview on LottieFiles.com
☐ Check file size (<100 KB ideal)
☐ Test in target browser/device
☐ Verify animation timing
☐ Check colors/rendering
☐ Test playback controls
☐ Verify loop behavior
☐ Test on mobile devices
```
## Resources
- **LottieFiles Preview:** https://lottiefiles.com/preview
- **Supported Features:** https://airbnb.io/lottie/#/supported-features
- **Bodymovin Plugin:** https://aescripts.com/bodymovin/
- **LottieFiles Plugin:** https://lottiefiles.com/plugins/after-effects
- **File Size Optimizer:** https://lottiefiles.com/tools/lottie-editor
## Troubleshooting
### Bodymovin Panel Not Showing
```
1. Check installation path is correct
2. Restart After Effects completely
3. Enable "Allow Scripts to Write Files"
(Edit → Preferences → Scripting & Expressions)
4. Reinstall using ZXP Installer
```
### Export Fails/Crashes
```
1. Update to latest After Effects version
2. Update Bodymovin plugin
3. Simplify composition (remove complex features)
4. Export smaller sections separately
5. Check After Effects error log
```
### Animation Too Large
```
1. Export as dotLottie (90% smaller)
2. Reduce composition dimensions
3. Simplify paths (fewer anchor points)
4. Remove embedded images
5. Reduce keyframe count
6. Use solid colors over gradients
```
references/api_reference.md
# Lottie API Reference
Complete API documentation for lottie-web, lottie-react, dotlottie-web, and dotlottie-react libraries.
## dotlottie-web API
### DotLottie Constructor
```javascript
import { DotLottie } from '@lottiefiles/dotlottie-web';
const dotLottie = new DotLottie(config);
```
**Config Options:**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `canvas` | HTMLCanvasElement | **required** | The canvas element to render the animation |
| `src` | string | **required** | URL to .lottie or .json file |
| `autoplay` | boolean | `true` | Auto-start animation on load |
| `loop` | boolean | `true` | Loop animation continuously |
| `speed` | number | `1.0` | Playback speed multiplier (0.5 = half speed, 2 = double speed) |
| `mode` | string | `'forward'` | Playback mode: `'forward'`, `'reverse'`, `'bounce'`, `'reverse-bounce'` |
| `backgroundColor` | string | `null` | Canvas background color |
| `renderConfig` | object | `{}` | Rendering configuration |
| `data` | string \| ArrayBuffer | `null` | Inline animation data (alternative to src) |
| `marker` | string | `null` | Named marker to play |
| `segment` | [number, number] | `null` | Frame range to play [startFrame, endFrame] |
| `useFrameInterpolation` | boolean | `true` | Smooth frame transitions |
**Render Config Options:**
```javascript
renderConfig: {
devicePixelRatio: window.devicePixelRatio || 1, // Pixel density
freezeOnOffscreen: false, // Pause when not visible
imageRendering: 'auto' // 'auto', 'crisp-edges', 'pixelated'
}
```
### DotLottie Methods
**Playback Control:**
```javascript
dotLottie.play(); // Play animation
dotLottie.pause(); // Pause animation
dotLottie.stop(); // Stop and reset to frame 0
dotLottie.setSpeed(speed); // Set playback speed (e.g., 2 for double speed)
dotLottie.setLoop(loop); // Enable/disable looping
dotLottie.setMode(mode); // Set playback mode ('forward', 'reverse', 'bounce')
```
**Frame Navigation:**
```javascript
dotLottie.setFrame(frame); // Seek to specific frame (0-indexed)
dotLottie.goToAndPlay(frame, isFrame); // Jump to frame/time and play
dotLottie.goToAndStop(frame, isFrame); // Jump to frame/time and stop
dotLottie.playSegments(segments, forceFlag); // Play frame range
```
**Animation Management:**
```javascript
dotLottie.loadAnimation(animationId); // Load specific animation (multi-animation files)
dotLottie.setTheme(themeId); // Set theme (multi-theme files)
dotLottie.resize(); // Resize to canvas dimensions
dotLottie.destroy(); // Cleanup and destroy instance
```
**State Machine (Interactive Lottie):**
```javascript
dotLottie.loadStateMachine(stateMachineId); // Load state machine
dotLottie.startStateMachine(); // Start state machine
dotLottie.stopStateMachine(); // Stop state machine
dotLottie.postStateMachineEvent(event); // Post event to state machine
```
**State Machine Events:**
```javascript
dotLottie.postStateMachineEvent("Bool: true");
dotLottie.postStateMachineEvent("Bool: false");
dotLottie.postStateMachineEvent("String: example");
dotLottie.postStateMachineEvent("Numeric: 42.5");
dotLottie.postStateMachineEvent("OnPointerDown: 100 200"); // x, y
dotLottie.postStateMachineEvent("OnPointerUp: 100 200");
dotLottie.postStateMachineEvent("OnPointerMove: 100 200");
dotLottie.postStateMachineEvent("OnPointerEnter: 100 200");
dotLottie.postStateMachineEvent("OnPointerExit: 100 200");
dotLottie.postStateMachineEvent("OnComplete");
```
**Layer Information:**
```javascript
const boundingBox = dotLottie.getLayerBoundingBox(layerName);
// Returns: { x, y, width, height } or null
```
### DotLottie Properties
```javascript
dotLottie.currentFrame; // Current frame number (read-only)
dotLottie.totalFrames; // Total number of frames (read-only)
dotLottie.duration; // Animation duration in seconds (read-only)
dotLottie.isPlaying; // Boolean: is currently playing (read-only)
dotLottie.isLoaded; // Boolean: animation loaded (read-only)
dotLottie.manifest; // Manifest object (animations, themes) (read-only)
dotLottie.activeAnimationId; // Current animation ID (read-only)
dotLottie.activeThemeId; // Current theme ID (read-only)
```
### DotLottie Events
```javascript
// Add event listeners
dotLottie.addEventListener('load', onLoad);
dotLottie.addEventListener('play', onPlay);
dotLottie.addEventListener('pause', onPause);
dotLottie.addEventListener('stop', onStop);
dotLottie.addEventListener('complete', onComplete);
dotLottie.addEventListener('loopComplete', onLoopComplete);
dotLottie.addEventListener('frame', onFrame);
dotLottie.addEventListener('render', onRender);
dotLottie.addEventListener('destroy', onDestroy);
// Remove event listeners
dotLottie.removeEventListener('load', onLoad);
```
**Event Callbacks:**
```javascript
function onLoad() {
console.log('Animation loaded');
}
function onPlay() {
console.log('Animation started');
}
function onPause() {
console.log('Animation paused');
}
function onStop() {
console.log('Animation stopped');
}
function onComplete() {
console.log('Animation completed');
}
function onLoopComplete() {
console.log('Loop completed');
}
function onFrame({ currentFrame }) {
console.log('Current frame:', currentFrame);
}
function onRender() {
console.log('Frame rendered');
}
function onDestroy() {
console.log('Instance destroyed');
}
```
---
## DotLottieWorker API
**Purpose:** Offload animation rendering to a Web Worker for better performance.
```javascript
import { DotLottieWorker } from '@lottiefiles/dotlottie-web';
new DotLottieWorker({
canvas: document.getElementById('canvas'),
src: 'animation.lottie',
autoplay: true,
loop: true,
workerId: 'worker-1' // Optional: group multiple animations by worker
});
```
**Config:** Same as `DotLottie`, plus:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `workerId` | string | auto-generated | Worker ID for grouping animations |
**Use Case:** Complex animations that cause main thread lag.
---
## dotlottie-react API
### DotLottieReact Component
```jsx
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
<DotLottieReact
src="animation.lottie"
loop
autoplay
speed={1}
mode="forward"
backgroundColor="#ffffff"
style={{ height: 400, width: 400 }}
className="lottie-animation"
dotLottieRefCallback={(instance) => setDotLottie(instance)}
animationId="animation-1"
themeId="light"
marker="intro"
segment={[0, 60]}
useFrameInterpolation
data={inlineData}
renderConfig={{ devicePixelRatio: 2 }}
/>
```
**Props:**
All `DotLottie` config options are available as props, plus:
| Prop | Type | Description |
|------|------|-------------|
| `dotLottieRefCallback` | function | Callback to get dotLottie instance |
| `style` | object | Inline styles for wrapper div |
| `className` | string | CSS class for wrapper div |
**Getting Instance Reference:**
```jsx
const [dotLottie, setDotLottie] = useState(null);
<DotLottieReact
src="animation.lottie"
dotLottieRefCallback={setDotLottie}
/>
// Use instance
useEffect(() => {
if (dotLottie) {
dotLottie.play();
}
}, [dotLottie]);
```
---
## lottie-web API (Original Library)
### lottie.loadAnimation()
```javascript
import lottie from 'lottie-web';
const animation = lottie.loadAnimation({
container: document.getElementById('lottie-container'), // Required
renderer: 'svg', // 'svg', 'canvas', 'html'
loop: true,
autoplay: true,
path: 'animation.json', // URL to animation
// OR
animationData: jsonData, // Inline JSON data
name: 'my-animation', // Optional name
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
progressiveLoad: false,
hideOnTransparent: true,
className: 'lottie-svg', // SVG class
scaleMode: 'noScale' // 'noScale', 'fill', 'fit'
}
});
```
### lottie-web Methods
**Playback:**
```javascript
animation.play();
animation.pause();
animation.stop();
animation.setSpeed(speed); // 1 = normal, 0.5 = half speed
animation.setDirection(direction); // 1 = forward, -1 = reverse
animation.goToAndPlay(frame, isFrame);
animation.goToAndStop(frame, isFrame);
animation.playSegments(segments, forceFlag);
```
**Segments:**
```javascript
// Play frames 0-30 only
animation.playSegments([0, 30], false);
// Play multiple segments
animation.playSegments([[0, 30], [60, 90]], false);
```
**Properties:**
```javascript
animation.totalFrames;
animation.currentFrame;
animation.frameRate;
animation.isLoaded;
animation.isPaused;
animation.renderer; // 'svg', 'canvas', 'html'
animation.name;
```
**Utility:**
```javascript
animation.resize();
animation.setSubframe(useSubframes); // Smooth subframe rendering
animation.getDuration(inFrames); // Duration in frames or seconds
animation.destroy();
```
### lottie-web Events
```javascript
animation.addEventListener('DOMLoaded', onDOMLoaded);
animation.addEventListener('data_ready', onDataReady);
animation.addEventListener('config_ready', onConfigReady);
animation.addEventListener('complete', onComplete);
animation.addEventListener('loopComplete', onLoopComplete);
animation.addEventListener('enterFrame', onEnterFrame);
animation.addEventListener('segmentStart', onSegmentStart);
animation.addEventListener('destroy', onDestroy);
animation.removeEventListener('complete', onComplete);
```
**Event Callbacks:**
```javascript
function onEnterFrame(event) {
console.log('Current frame:', event.currentTime);
console.log('Direction:', event.direction); // 1 or -1
}
function onLoopComplete(event) {
console.log('Loop completed:', event.currentLoop);
}
```
### lottie Global Methods
```javascript
lottie.play(name); // Play animation by name
lottie.pause(name); // Pause animation by name
lottie.stop(name); // Stop animation by name
lottie.setSpeed(speed, name); // Set speed for named animation
lottie.setDirection(direction, name);
lottie.destroy(name); // Destroy animation by name
lottie.loadAnimation(config); // Load new animation
lottie.searchAnimations(); // Auto-detect and load animations
```
---
## lottie-react API
### Lottie Component
```jsx
import Lottie from 'lottie-react';
import animationData from './animation.json';
<Lottie
animationData={animationData} // Required
loop={true}
autoplay={true}
initialSegment={[0, 60]}
onComplete={onComplete}
onLoopComplete={onLoopComplete}
onEnterFrame={onEnterFrame}
onSegmentStart={onSegmentStart}
onConfigReady={onConfigReady}
onDataReady={onDataReady}
onDataFailed={onDataFailed}
onLoadedImages={onLoadedImages}
onDOMLoaded={onDOMLoaded}
onDestroy={onDestroy}
style={{ height: 400 }}
className="lottie-animation"
lottieRef={lottieRef}
interactivity={interactivityConfig}
/>
```
**Props:**
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `animationData` | object | **required** | Lottie JSON data |
| `loop` | boolean \| number | `true` | Loop animation (true = infinite, number = loop count) |
| `autoplay` | boolean | `true` | Auto-start on load |
| `initialSegment` | [number, number] | `null` | Start frame range |
| `onComplete` | function | `null` | Callback on animation complete |
| `onLoopComplete` | function | `null` | Callback on loop complete |
| `onEnterFrame` | function | `null` | Callback on each frame |
| `onSegmentStart` | function | `null` | Callback on segment start |
| `onConfigReady` | function | `null` | Callback on config ready |
| `onDataReady` | function | `null` | Callback on data ready |
| `onDataFailed` | function | `null` | Callback on data load failure |
| `onLoadedImages` | function | `null` | Callback on images loaded |
| `onDOMLoaded` | function | `null` | Callback on DOM loaded |
| `onDestroy` | function | `null` | Callback on destroy |
| `style` | object | `{}` | Inline styles |
| `className` | string | `''` | CSS class |
| `lottieRef` | React.RefObject | `null` | Ref to lottie instance |
| `interactivity` | object | `null` | Interactivity config (scroll/cursor) |
### Using lottieRef
```jsx
const lottieRef = useRef();
<Lottie animationData={animationData} lottieRef={lottieRef} />
// Access instance
useEffect(() => {
if (lottieRef.current) {
lottieRef.current.setSpeed(2);
lottieRef.current.goToAndPlay(30, true);
}
}, []);
```
### Interactivity Config
**Scroll Mode:**
```javascript
const interactivity = {
mode: 'scroll',
actions: [
{
visibility: [0, 0.3], // When 0-30% visible
type: 'stop',
frames: [0]
},
{
visibility: [0.3, 1], // When 30-100% visible
type: 'seek',
frames: [0, 60]
}
]
};
```
**Cursor Mode:**
```javascript
const interactivity = {
mode: 'cursor',
actions: [
{
position: { x: [0, 1], y: [0, 1] }, // Inside container
type: 'loop',
frames: [0, 60]
},
{
position: { x: -1, y: -1 }, // Outside container
type: 'stop',
frames: [0]
}
]
};
```
**Action Types:**
- `'stop'` - Stop at specified frame
- `'play'` - Play from current frame
- `'loop'` - Loop specified segment
- `'seek'` - Scrub through frames based on scroll/cursor position
---
## useLottie Hook
```javascript
import { useLottie } from 'lottie-react';
import animationData from './animation.json';
const { View, play, pause, stop, setSpeed, goToAndPlay, goToAndStop } = useLottie({
animationData: animationData,
loop: true,
autoplay: true,
initialSegment: [0, 60]
}, style);
```
**Returns:**
| Property | Type | Description |
|----------|------|-------------|
| `View` | React.Element | Rendered Lottie component |
| `play` | function | Play animation |
| `pause` | function | Pause animation |
| `stop` | function | Stop animation |
| `setSpeed` | function | Set playback speed |
| `setDirection` | function | Set direction (1 or -1) |
| `goToAndPlay` | function | Jump to frame and play |
| `goToAndStop` | function | Jump to frame and stop |
| `playSegments` | function | Play specific segments |
| `setSubframe` | function | Enable subframe rendering |
| `getDuration` | function | Get animation duration |
| `destroy` | function | Destroy instance |
---
## useLottieInteractivity Hook
```javascript
import { useLottie, useLottieInteractivity } from 'lottie-react';
const lottieObj = useLottie({ animationData });
const Animation = useLottieInteractivity({
lottieObj,
mode: 'scroll', // or 'cursor'
actions: [
{
visibility: [0, 1],
type: 'seek',
frames: [0, 60]
}
]
});
return Animation;
```
---
## Best Practices
1. **Always destroy instances on unmount:**
```javascript
useEffect(() => {
return () => {
dotLottie?.destroy();
};
}, [dotLottie]);
```
2. **Use dotLottie format (.lottie) for production** - smaller file sizes
3. **Prefer Canvas renderer for complex animations** - better performance
4. **Use Web Workers (DotLottieWorker) for heavy animations** - offload from main thread
5. **Lazy load animations** - only load when visible (IntersectionObserver)
6. **Clean up event listeners:**
```javascript
useEffect(() => {
const handler = () => {};
dotLottie?.addEventListener('complete', handler);
return () => dotLottie?.removeEventListener('complete', handler);
}, [dotLottie]);
```
7. **Use animationData prop (not path) for bundled animations** - faster, no network request
references/performance_guide.md
# Lottie Performance Optimization Guide
Comprehensive strategies for optimizing Lottie animation performance in production.
## Performance Metrics
**Target Performance:**
- **Desktop:** 60 FPS minimum
- **Mobile:** 30-60 FPS
- **File Size:** <100 KB ideal, <200 KB acceptable
- **Load Time:** <500ms for animation ready
- **Memory:** <50 MB per animation instance
## File Size Optimization
### 1. Use dotLottie Format
```javascript
// 90% smaller file size
<DotLottieReact src="animation.lottie" /> // ✅ 10-50 KB
// vs
<Lottie animationData={jsonData} /> // ❌ 100-500 KB
```
**Benefits:**
- ZIP compression (up to 90% reduction)
- Multiple animations in one file
- Theme support
- Faster parsing
### 2. Optimize JSON
**Before Export (After Effects):**
```
- Simplify paths (reduce anchor points)
- Merge shapes when possible
- Remove unused layers/keyframes
- Use solid colors over gradients
- Enable "Compress JSON" in Bodymovin
```
**After Export:**
```bash
# Use Lottie optimizer
https://lottiefiles.com/tools/lottie-editor
# Or gzip compression (server-side)
gzip animation.json # 60-80% reduction
```
### 3. Lazy Loading
```jsx
import { lazy, Suspense } from 'react';
const LottieAnimation = lazy(() => import('./LottieAnimation'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LottieAnimation />
</Suspense>
);
}
```
**Intersection Observer Pattern:**
```jsx
const LazyLottie = ({ src }) => {
const [shouldLoad, setShouldLoad] = useState(false);
const ref = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setShouldLoad(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={ref}>
{shouldLoad ? <DotLottieReact src={src} /> : <div style={{ height: 300 }} />}
</div>
);
};
```
## Runtime Performance
### 1. Renderer Selection
**SVG Renderer:**
```javascript
// Best quality, slower performance
// Use for: Simple icons, small animations, few instances
lottie.loadAnimation({
renderer: 'svg', // Vector-based, scalable
container: element,
path: 'animation.json'
});
```
**Canvas Renderer:**
```javascript
// Better performance, rasterized
// Use for: Complex animations, many instances, mobile
new DotLottie({
canvas: canvasElement, // ✅ Recommended for complex animations
src: 'animation.lottie'
});
```
**Performance Comparison:**
| Renderer | Quality | Performance | Use Case |
|----------|---------|-------------|----------|
| SVG | Excellent | Good (simple animations) | Icons, logos, simple UI |
| Canvas | Good | Excellent | Complex animations, mobile |
| HTML | Poor | Poor | Legacy support only |
### 2. Web Workers (Best Performance)
```javascript
import { DotLottieWorker } from '@lottiefiles/dotlottie-web';
// Offload rendering to separate thread
new DotLottieWorker({
canvas: document.getElementById('canvas'),
src: 'heavy-animation.lottie',
autoplay: true,
loop: true,
workerId: 'worker-1'
});
```
**Benefits:**
- No main thread blocking
- Smooth scroll performance
- Multiple animations without lag
- Better mobile performance
**When to use:**
- Animation >100 KB
- Multiple simultaneous animations
- Scroll-driven animations
- Mobile devices
### 3. Device Pixel Ratio Optimization
```javascript
// Reduce quality on low-end devices
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const isLowEnd = navigator.deviceMemory < 4; // GB
new DotLottie({
canvas: canvas,
src: 'animation.lottie',
renderConfig: {
devicePixelRatio: (isMobile || isLowEnd) ? 1 : window.devicePixelRatio
// 1 = standard definition (faster)
// 2 = retina (slower, better quality)
}
});
```
### 4. Animation Complexity Control
**Adaptive Quality:**
```javascript
const getAnimationSrc = () => {
const isMobile = /Mobile/i.test(navigator.userAgent);
const isSlowDevice = navigator.hardwareConcurrency < 4;
if (isMobile || isSlowDevice) {
return 'animation-low.lottie'; // Simplified version
}
return 'animation-high.lottie'; // Full quality
};
<DotLottieReact src={getAnimationSrc()} />
```
**Frame Rate Reduction:**
```javascript
// Reduce frame rate on slow devices
const targetFPS = isMobile ? 30 : 60;
new DotLottie({
canvas: canvas,
src: 'animation.lottie',
speed: 1,
// Implemented via requestAnimationFrame throttling
});
```
## Memory Management
### 1. Proper Cleanup
```jsx
const AnimationComponent = () => {
const [dotLottie, setDotLottie] = useState(null);
useEffect(() => {
return () => {
// CRITICAL: Destroy on unmount
dotLottie?.destroy();
};
}, [dotLottie]);
return <DotLottieReact dotLottieRefCallback={setDotLottie} />;
};
```
### 2. Event Listener Cleanup
```javascript
useEffect(() => {
if (!dotLottie) return;
const handleComplete = () => console.log('Done');
dotLottie.addEventListener('complete', handleComplete);
// CRITICAL: Remove listeners
return () => {
dotLottie.removeEventListener('complete', handleComplete);
};
}, [dotLottie]);
```
### 3. Instance Pooling
```javascript
// Reuse animation instances
class LottiePool {
constructor() {
this.pool = [];
this.active = new Set();
}
acquire(src) {
let instance = this.pool.find(i => i.src === src && !this.active.has(i));
if (!instance) {
instance = new DotLottie({ canvas: createCanvas(), src });
this.pool.push(instance);
}
this.active.add(instance);
return instance;
}
release(instance) {
instance.stop();
this.active.delete(instance);
}
cleanup() {
this.pool.forEach(i => i.destroy());
this.pool = [];
this.active.clear();
}
}
```
## Network Optimization
### 1. CDN Hosting
```jsx
// Host on CDN for better caching
<DotLottieReact
src="https://cdn.example.com/animations/hero.lottie"
loop
autoplay
/>
```
**Benefits:**
- Edge caching (faster loads)
- Reduced server load
- Better global performance
### 2. Preloading Critical Animations
```html
<!-- Preload animation for faster display -->
<link rel="preload" href="/animations/hero.lottie" as="fetch" crossorigin>
```
```javascript
// Programmatic preload
const preloadAnimation = async (src) => {
const response = await fetch(src);
const blob = await response.blob();
return URL.createObjectURL(blob);
};
// Use preloaded blob
const blobUrl = await preloadAnimation('/animation.lottie');
<DotLottieReact src={blobUrl} />
```
### 3. Service Worker Caching
```javascript
// service-worker.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('lottie-v1').then((cache) => {
return cache.addAll([
'/animations/hero.lottie',
'/animations/loading.lottie'
]);
})
);
});
self.addEventListener('fetch', (event) => {
if (event.request.url.endsWith('.lottie')) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
}
});
```
## Mobile Optimization
### 1. Reduce Quality on Mobile
```javascript
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
<DotLottieReact
src={isMobile ? 'animation-mobile.lottie' : 'animation-desktop.lottie'}
renderConfig={{
devicePixelRatio: isMobile ? 1 : window.devicePixelRatio
}}
/>
```
### 2. Pause on Offscreen
```javascript
useEffect(() => {
if (!dotLottie) return;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
dotLottie.play();
} else {
dotLottie.pause();
}
});
observer.observe(document.getElementById('lottie-container'));
return () => observer.disconnect();
}, [dotLottie]);
```
### 3. Battery-Aware Performance
```javascript
// Reduce animation when low battery
navigator.getBattery?.().then((battery) => {
if (battery.level < 0.2) {
dotLottie.setSpeed(0.5); // Slower = less CPU
}
battery.addEventListener('levelchange', () => {
if (battery.level < 0.2) {
dotLottie.stop();
}
});
});
```
## Monitoring Performance
### 1. FPS Monitoring
```javascript
class FPSMonitor {
constructor() {
this.fps = 0;
this.frames = 0;
this.lastTime = performance.now();
}
update() {
this.frames++;
const currentTime = performance.now();
if (currentTime - this.lastTime >= 1000) {
this.fps = Math.round((this.frames * 1000) / (currentTime - this.lastTime));
console.log(`FPS: ${this.fps}`);
this.frames = 0;
this.lastTime = currentTime;
}
requestAnimationFrame(() => this.update());
}
start() {
this.update();
}
}
const monitor = new FPSMonitor();
monitor.start();
```
### 2. Memory Usage Tracking
```javascript
if (performance.memory) {
setInterval(() => {
const used = (performance.memory.usedJSHeapSize / 1048576).toFixed(2);
const total = (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2);
console.log(`Memory: ${used} MB / ${total} MB`);
}, 5000);
}
```
### 3. Performance Marks
```javascript
// Mark start
performance.mark('lottie-load-start');
const dotLottie = new DotLottie({...});
dotLottie.addEventListener('load', () => {
// Mark end and measure
performance.mark('lottie-load-end');
performance.measure('lottie-load', 'lottie-load-start', 'lottie-load-end');
const measure = performance.getEntriesByName('lottie-load')[0];
console.log(`Load time: ${measure.duration}ms`);
});
```
## Common Performance Issues
### Issue 1: Animation Stutters
**Symptoms:** Dropped frames, jerky playback
**Solutions:**
```
1. Switch to Canvas renderer
2. Use DotLottieWorker for web worker rendering
3. Reduce devicePixelRatio (1 instead of 2)
4. Simplify animation (fewer shapes/keyframes)
5. Reduce number of simultaneous animations
```
### Issue 2: Slow Page Load
**Symptoms:** Long initial render time
**Solutions:**
```
1. Use lazy loading with IntersectionObserver
2. Preload critical animations only
3. Use dotLottie format (smaller files)
4. Load animations after page interactive
5. Use code splitting (React.lazy)
```
### Issue 3: Memory Leaks
**Symptoms:** Memory usage increases over time
**Solutions:**
```
1. Call destroy() on unmount
2. Remove all event listeners
3. Clear animation references
4. Avoid creating new instances repeatedly
5. Use instance pooling for frequently re-created animations
```
### Issue 4: Mobile Performance Poor
**Symptoms:** Slow on phones, smooth on desktop
**Solutions:**
```
1. Create mobile-optimized version (simpler)
2. Use Canvas renderer (not SVG)
3. Set devicePixelRatio to 1
4. Reduce animation frame rate (30 FPS)
5. Use DotLottieWorker
6. Pause when offscreen
```
## Best Practices Summary
**File Size:**
- ✅ Use dotLottie format (.lottie)
- ✅ Compress JSON
- ✅ Keep under 100 KB
- ✅ Optimize images (or remove)
- ✅ Simplify paths
**Runtime:**
- ✅ Use Canvas renderer for complex animations
- ✅ Use Web Workers (DotLottieWorker)
- ✅ Destroy instances on unmount
- ✅ Remove event listeners
- ✅ Lazy load animations
**Mobile:**
- ✅ Create mobile-optimized versions
- ✅ Set devicePixelRatio to 1
- ✅ Pause when offscreen
- ✅ Reduce quality on low-end devices
**Monitoring:**
- ✅ Track FPS (target 60 on desktop, 30 on mobile)
- ✅ Monitor memory usage
- ✅ Measure load times
- ✅ Test on real devices
scripts/generate_lottie_component.py
#!/usr/bin/env python3
"""
Lottie Component Generator
Generates React, Vue, or Svelte Lottie component boilerplate with common patterns.
Usage:
./generate_lottie_component.py # Interactive mode
./generate_lottie_component.py --framework react --type basic
./generate_lottie_component.py --framework vue --type interactive
"""
import argparse
import sys
TEMPLATES = {
'react_basic': '''import React from 'react';
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
export const {ComponentName} = () => {
return (
<DotLottieReact
src="{animationSrc}"
loop
autoplay
style=\u007b\u007b height: {height}, width: {width} \u007d\u007d
/>
);
};
''',
'react_interactive': '''import React, \u007b useState \u007d from 'react';
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
export const {ComponentName} = () => {
const [dotLottie, setDotLottie] = useState(null);
const handlePlay = () => dotLottie?.play();
const handlePause = () => dotLottie?.pause();
const handleStop = () => dotLottie?.stop();
return (
<div>
<DotLottieReact
src="{animationSrc}"
loop
autoplay=\u007bfalse\u007d
dotLottieRefCallback=\u007bsetDotLottie\u007d
style=\u007b\u007b height: {height}, width: {width} \u007d\u007d
/>
<div style=\u007b\u007b marginTop: 16 \u007d\u007d>
<button onClick=\u007bhandlePlay\u007d>Play</button>
<button onClick=\u007bhandlePause\u007d>Pause</button>
<button onClick=\u007bhandleStop\u007d>Stop</button>
</div>
</div>
);
};
''',
'vue_basic': '''<script setup>
import { DotLottieVue } from '@lottiefiles/dotlottie-vue';
</script>
<template>
<DotLottieVue
src="{animationSrc}"
:autoplay="true"
:loop="true"
:style="{ height: '{height}px', width: '{width}px' }"
/>
</template>
''',
'svelte_basic': '''<script lang="ts">
import { DotLottieSvelte } from '@lottiefiles/dotlottie-svelte';
</script>
<DotLottieSvelte
src="{animationSrc}"
loop=\u007btrue\u007d
autoplay=\u007btrue\u007d
style="height: {height}px; width: {width}px;"
/>
'''
}
def generate_component(framework, component_type, component_name, animation_src, height, width):
"""Generate component code based on parameters."""
key = f"{framework}_{component_type}"
if key not in TEMPLATES:
print(f"Error: Template '{key}' not found")
sys.exit(1)
template = TEMPLATES[key]
code = template.format(
ComponentName=component_name,
animationSrc=animation_src,
height=height,
width=width
)
return code
def interactive_mode():
"""Run interactive mode to gather component parameters."""
print("Lottie Component Generator")
print("-" * 40)
# Framework selection
print("\nSelect framework:")
print("1. React")
print("2. Vue")
print("3. Svelte")
framework_choice = input("Enter choice (1-3): ").strip()
framework_map = {'1': 'react', '2': 'vue', '3': 'svelte'}
framework = framework_map.get(framework_choice, 'react')
# Component type
if framework == 'react':
print("\nSelect component type:")
print("1. Basic (just displays animation)")
print("2. Interactive (with playback controls)")
type_choice = input("Enter choice (1-2): ").strip()
component_type = 'interactive' if type_choice == '2' else 'basic'
else:
component_type = 'basic'
# Component details
component_name = input("\nComponent name (e.g., HeroAnimation): ").strip() or "LottieAnimation"
animation_src = input("Animation source URL or path: ").strip() or "/animations/animation.lottie"
height = input("Height in pixels (default 400): ").strip() or "400"
width = input("Width in pixels (default 400): ").strip() or "400"
code = generate_component(framework, component_type, component_name, animation_src, height, width)
# Output
extensions = {'react': 'jsx', 'vue': 'vue', 'svelte': 'svelte'}
filename = f"{component_name}.{extensions[framework]}"
print(f"\nGenerated {filename}:")
print("-" * 40)
print(code)
save = input("\nSave to file? (y/n): ").strip().lower()
if save == 'y':
with open(filename, 'w') as f:
f.write(code)
print(f"✅ Saved to {filename}")
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description="Generate Lottie component boilerplate")
parser.add_argument('--framework', choices=['react', 'vue', 'svelte'], help="Framework to use")
parser.add_argument('--type', choices=['basic', 'interactive'], default='basic', help="Component type")
parser.add_argument('--name', default='LottieAnimation', help="Component name")
parser.add_argument('--src', default='/animations/animation.lottie', help="Animation source")
parser.add_argument('--height', default='400', help="Height in pixels")
parser.add_argument('--width', default='400', help="Width in pixels")
parser.add_argument('--output', help="Output file path")
args = parser.parse_args()
# Interactive mode if no framework specified
if not args.framework:
interactive_mode()
return
code = generate_component(
args.framework,
args.type,
args.name,
args.src,
args.height,
args.width
)
if args.output:
with open(args.output, 'w') as f:
f.write(code)
print(f"✅ Generated {args.output}")
else:
print(code)
if __name__ == '__main__':
main()
scripts/optimize_lottie.py
#!/usr/bin/env python3
"""
Lottie JSON Optimizer
Optimizes Lottie JSON files by removing whitespace, rounding numbers, and removing unnecessary data.
Usage:
./optimize_lottie.py animation.json # Output to stdout
./optimize_lottie.py animation.json -o optimized.json
"""
import json
import argparse
import sys
def round_number(value, precision=2):
"""Round number to specified precision."""
if isinstance(value, (int, float)):
return round(value, precision)
return value
def optimize_object(obj, precision=2):
"""Recursively optimize object by rounding numbers."""
if isinstance(obj, dict):
return {k: optimize_object(v, precision) for k, v in obj.items()}
elif isinstance(obj, list):
return [optimize_object(item, precision) for item in obj]
elif isinstance(obj, float):
return round(obj, precision)
return obj
def optimize_lottie(input_path, output_path=None, precision=2):
"""Optimize Lottie JSON file."""
try:
# Read input
with open(input_path, 'r') as f:
data = json.load(f)
# Optimize
optimized = optimize_object(data, precision)
# Write output
json_str = json.dumps(optimized, separators=(',', ':'))
if output_path:
with open(output_path, 'w') as f:
f.write(json_str)
print(f"✅ Optimized: {input_path} → {output_path}")
# Show size reduction
import os
original_size = os.path.getsize(input_path)
optimized_size = os.path.getsize(output_path)
reduction = ((original_size - optimized_size) / original_size) * 100
print(f" Original: {original_size:,} bytes")
print(f" Optimized: {optimized_size:,} bytes")
print(f" Reduction: {reduction:.1f}%")
else:
print(json_str)
except FileNotFoundError:
print(f"Error: File '{input_path}' not found", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON - {e}", file=sys.stderr)
sys.exit(1)
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description="Optimize Lottie JSON files")
parser.add_argument('input', help="Input Lottie JSON file")
parser.add_argument('-o', '--output', help="Output file path")
parser.add_argument('-p', '--precision', type=int, default=2, help="Number precision (default: 2)")
args = parser.parse_args()
optimize_lottie(args.input, args.output, args.precision)
if __name__ == '__main__':
main()
SKILL.md
---
name: lottie-animations
description: 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 Animations
## Overview
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:**
- Designer-created animations that need pixel-perfect fidelity
- Complex animated icons and micro-interactions
- Loading animations and progress indicators
- Onboarding sequences and tutorial animations
- Marketing animations and promotional content
- Alternative to GIF/video with smaller file sizes and scalability
**Key advantages:**
- Vector-based (scalable without quality loss)
- Significantly smaller file sizes than GIF or video
- Editable at runtime (colors, speed, segments)
- Full designer control via After Effects
- Cross-platform rendering consistency
- Interactive controls (play, pause, seek, loop)
## Core Concepts
### Lottie Format Types
**1. JSON Lottie (.json)**
- Original Lottie format
- Exported from After Effects via Bodymovin plugin
- Human-readable JSON structure
- Larger file sizes (not compressed)
- Widely supported across all platforms
**2. dotLottie (.lottie)**
- Modern compressed format
- ZIP archive containing JSON + assets
- Supports multiple animations and themes in one file
- Smaller file sizes (up to 90% reduction)
- Recommended for production use
### Library Options
**lottie-web** (original library):
```javascript
import lottie from 'lottie-web';
lottie.loadAnimation({
container: document.getElementById('lottie-container'),
renderer: 'svg', // or 'canvas', 'html'
loop: true,
autoplay: true,
path: 'animation.json' // or animationData: jsonData
});
```
**@lottiefiles/dotlottie-web** (modern, recommended):
```javascript
import { DotLottie } from '@lottiefiles/dotlottie-web';
new DotLottie({
canvas: document.getElementById('canvas'),
src: 'animation.lottie',
autoplay: true,
loop: true
});
```
**@lottiefiles/dotlottie-react** (React integration):
```jsx
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
<DotLottieReact
src="animation.lottie"
loop
autoplay
style={{ height: 300 }}
/>
```
**lottie-react** (alternative React wrapper):
```jsx
import Lottie from 'lottie-react';
import animationData from './animation.json';
<Lottie animationData={animationData} loop={true} />
```
### Animation Data Sources
**1. LottieFiles** (lottie.host)
- 100,000+ free animations
- Direct URL embedding
- CDN hosting
**2. Local JSON/dotLottie files**
- Bundled with application
- Better performance (no network request)
- Version control friendly
**3. After Effects export**
- Custom designer animations
- Bodymovin plugin required
- Export settings critical for file size
## Common Patterns
### 1. Basic HTML Integration with dotLottie-web
```html
<!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>
```
### 2. React Component with Controls
```jsx
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>
);
};
```
### 3. Event Listeners and Lifecycle Hooks
```jsx
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}
/>
);
};
```
### 4. Scroll-Driven Animation with lottie-react
```jsx
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}
/>
);
};
```
### 5. Hover-Triggered Segment Playback
```jsx
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>;
};
```
### 6. Multi-Animation and Theme Support
```jsx
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>
);
};
```
### 7. Web Worker for Performance (DotLottieWorker)
```javascript
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'
});
```
## Integration Patterns
### With GSAP ScrollTrigger
```jsx
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>
);
};
```
### With Framer Motion
```jsx
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>
);
};
```
### Vue 3 Integration
```vue
<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>
```
### Svelte Integration
```svelte
<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>
```
## Performance Optimization
### File Size Optimization
**1. Export Settings in After Effects:**
- Enable "Skip images that aren't used"
- Use "Glyphs" instead of fonts when possible
- Simplify paths (reduce points in illustrator)
- Avoid effects that create large data (particles, noise)
- Use shape layers instead of vector layers
**2. Compression:**
- Use dotLottie format (.lottie) for automatic compression
- Run JSON through Lottie optimizer tools
- Remove unnecessary metadata
**3. Lazy Loading:**
```jsx
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>
);
};
```
### Runtime Performance
**1. Renderer Selection:**
```javascript
// 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
}
});
```
**2. Web Workers:**
```javascript
// 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
});
```
**3. Mobile Optimization:**
```javascript
// 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
}
});
```
## Common Pitfalls
### 1. Memory Leaks from Improper Cleanup
**Problem:** Not destroying Lottie instances when components unmount.
**Solution:**
```jsx
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} />;
};
```
### 2. Event Listener Cleanup
**Problem:** Event listeners not removed, causing multiple handlers.
**Solution:**
```jsx
useEffect(() => {
if (!dotLottie) return;
const handleComplete = () => console.log('Complete');
dotLottie.addEventListener('complete', handleComplete);
// MUST return cleanup function
return () => {
dotLottie.removeEventListener('complete', handleComplete);
};
}, [dotLottie]);
```
### 3. Large File Sizes
**Problem:** Exported JSON files are 500KB+ for simple animations.
**Solutions:**
- Simplify After Effects composition (reduce layers, keyframes)
- Use dotLottie format for compression
- Check Bodymovin export settings (disable "Include expressions" if not needed)
- Remove unused assets before export
- Use Lottie optimizer tools: https://lottiefiles.com/tools/lottie-editor
### 4. Animation Performance Issues
**Problem:** Animation stutters or drops frames.
**Solutions:**
- Switch from SVG to Canvas renderer
- Use `DotLottieWorker` for web worker rendering
- Reduce complexity in After Effects (fewer layers, simpler shapes)
- Lower devicePixelRatio on mobile
- Avoid animating too many properties simultaneously
### 5. Incorrect Path/URL References
**Problem:** Animation doesn't load due to CORS or incorrect paths.
**Solution:**
```jsx
// 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" />
```
### 6. After Effects Export Compatibility
**Problem:** Some After Effects features don't export to Lottie.
**Unsupported features:**
- Layer effects (drop shadows, glows) - use shape layers instead
- Blending modes (limited support)
- 3D layers
- Expressions (partial support)
- Track mattes (partial support)
**Solution:**
- Test export early and often
- Use LottieFiles preview before exporting
- Check Bodymovin compatibility: https://airbnb.io/lottie/#/supported-features
- Convert effects to shapes when possible
## Resources
This skill includes:
### scripts/
- `generate_lottie_component.py` - Generate React/Vue/Svelte Lottie component boilerplate
- `optimize_lottie.py` - Optimize Lottie JSON file size
### references/
- `api_reference.md` - Complete API documentation for lottie-web, lottie-react, and dotlottie-web
- `after_effects_export.md` - Guide for exporting animations from After Effects
- `performance_guide.md` - Detailed performance optimization strategies
### assets/
- `starter_lottie/` - Complete React + Vite starter template with Lottie examples
- `examples/` - Real-world Lottie animation patterns and use cases
## Related Skills
- **gsap-scrolltrigger** - For scroll-driven Lottie animations synchronized with page scroll
- **motion-framer** - Combine with Framer Motion for layout animations wrapping Lottie
- **animated-component-libraries** - Pre-built components that may include Lottie animations
- **threejs-webgl** - For 3D animations beyond Lottie's 2D capabilities
- **react-three-fiber** - Alternative for complex 3D animated scenes