skills/awwwards-animations/references/animejs-react.md

664 lines
14 KiB
Markdown

# Anime.js 4.0 React Patterns
Complete Anime.js 4.0 patterns for React. Lightweight alternative for simple animations.
## Table of Contents
1. [Installation & Setup](#installation--setup)
2. [React Integration](#react-integration)
3. [Basic Animations](#basic-animations)
4. [Timeline](#timeline)
5. [Stagger](#stagger)
6. [Scroll Animations](#scroll-animations)
7. [Text Animations](#text-animations)
8. [SVG Animations](#svg-animations)
9. [Draggable](#draggable)
10. [v3 to v4 Migration](#v3-to-v4-migration)
## Installation & Setup
```bash
npm install animejs
```
### Imports (v4 Syntax)
```tsx
// Named imports (v4)
import {
animate,
createTimeline,
createScope,
createSpring,
createDraggable,
stagger,
svg,
utils,
engine
} from 'animejs'
```
## React Integration
### Basic Pattern with createScope
```tsx
'use client'
import { useEffect, useRef } from 'react'
import { animate, createScope } from 'animejs'
export function AnimatedBox() {
const rootRef = useRef<HTMLDivElement>(null)
const scopeRef = useRef<ReturnType<typeof createScope> | null>(null)
useEffect(() => {
// Create scope bound to container
scopeRef.current = createScope({ root: rootRef.current }).add(() => {
// All animations here are scoped to rootRef
animate('.box', {
translateX: 250,
rotate: '1turn',
duration: 800,
ease: 'out(3)',
})
})
// Cleanup on unmount
return () => scopeRef.current?.revert()
}, [])
return (
<div ref={rootRef}>
<div className="box">Animated</div>
</div>
)
}
```
### Registering Methods for External Control
```tsx
'use client'
import { useEffect, useRef } from 'react'
import { animate, createScope } from 'animejs'
export function ControlledAnimation() {
const rootRef = useRef<HTMLDivElement>(null)
const scopeRef = useRef<ReturnType<typeof createScope> | null>(null)
useEffect(() => {
scopeRef.current = createScope({ root: rootRef.current }).add((self) => {
// Register method accessible outside useEffect
self.add('animateBox', () => {
animate('.box', {
scale: [1, 1.2, 1],
duration: 400,
ease: 'out(2)',
})
})
})
return () => scopeRef.current?.revert()
}, [])
const handleClick = () => {
// Call registered method
scopeRef.current?.methods.animateBox()
}
return (
<div ref={rootRef}>
<button onClick={handleClick}>Animate</button>
<div className="box">Click to animate</div>
</div>
)
}
```
## Basic Animations
### Simple Animation
```tsx
// v4 syntax: animate(targets, { properties })
animate('.element', {
translateX: 250,
translateY: 100,
rotate: '1turn',
scale: 1.5,
opacity: 0.5,
duration: 1000,
ease: 'out(3)',
})
```
### From/To Values
```tsx
animate('.element', {
translateX: [0, 250], // from 0 to 250
opacity: [0, 1], // from 0 to 1
scale: [0.5, 1], // from 0.5 to 1
duration: 800,
})
```
### Keyframes
```tsx
animate('.element', {
keyframes: [
{ translateX: 0, scale: 1 },
{ translateX: 100, scale: 1.2 },
{ translateX: 200, scale: 1 },
{ translateX: 250, scale: 0.8 },
],
duration: 2000,
ease: 'inOut(2)',
})
```
### Property-Specific Parameters
```tsx
animate('.element', {
translateX: {
to: 250,
duration: 1000,
ease: 'out(4)',
},
rotate: {
to: '1turn',
duration: 1500,
ease: 'inOut(2)',
},
scale: {
to: 1.5,
duration: 800,
delay: 200,
},
})
```
### Callbacks
```tsx
animate('.element', {
translateX: 250,
duration: 1000,
onBegin: () => console.log('Started'),
onUpdate: (anim) => console.log(anim.progress),
onComplete: () => console.log('Done'),
onLoop: () => console.log('Loop'),
}).then(() => {
console.log('Promise resolved')
})
```
### Playback Controls
```tsx
const animation = animate('.element', {
translateX: 250,
autoplay: false,
})
animation.play() // Play forward
animation.pause() // Pause
animation.resume() // Resume in current direction
animation.reverse() // Play backward
animation.restart() // Restart from beginning
animation.seek(500) // Seek to 500ms
animation.reset() // Reset to initial state
```
## Timeline
### Basic Timeline
```tsx
import { createTimeline } from 'animejs'
const tl = createTimeline({
defaults: {
duration: 500,
ease: 'out(3)',
}
})
tl.add('.box-1', { translateX: 250 })
.add('.box-2', { translateX: 250 }, '-=200') // 200ms before previous ends
.add('.box-3', { translateX: 250 }, '+=100') // 100ms after previous ends
```
### Timeline in React
```tsx
'use client'
import { useEffect, useRef } from 'react'
import { createTimeline, createScope } from 'animejs'
export function TimelineAnimation() {
const rootRef = useRef<HTMLDivElement>(null)
const scopeRef = useRef<ReturnType<typeof createScope> | null>(null)
useEffect(() => {
scopeRef.current = createScope({ root: rootRef.current }).add(() => {
const tl = createTimeline({
defaults: { duration: 600, ease: 'out(3)' }
})
tl.add('.title', { opacity: [0, 1], translateY: [30, 0] })
.add('.subtitle', { opacity: [0, 1], translateY: [20, 0] }, '-=400')
.add('.cta', { opacity: [0, 1], scale: [0.9, 1] }, '-=300')
})
return () => scopeRef.current?.revert()
}, [])
return (
<div ref={rootRef}>
<h1 className="title">Title</h1>
<p className="subtitle">Subtitle</p>
<button className="cta">CTA</button>
</div>
)
}
```
## Stagger
### Basic Stagger
```tsx
import { stagger } from 'animejs'
animate('.grid-item', {
opacity: [0, 1],
translateY: [50, 0],
delay: stagger(100), // 100ms between each
duration: 600,
})
```
### Stagger with Start Value
```tsx
animate('.item', {
translateX: 250,
delay: stagger(100, { start: 500 }), // Start at 500ms, then +100ms each
})
```
### Grid Stagger
```tsx
animate('.grid-item', {
scale: [0, 1],
delay: stagger(50, {
grid: [4, 4], // 4x4 grid
from: 'center', // Animate from center outward
}),
})
```
### Stagger from Index
```tsx
animate('.item', {
opacity: [0, 1],
delay: stagger(100, {
from: 'first', // 'first', 'last', 'center', or index number
}),
})
```
### Value Stagger
```tsx
animate('.bar', {
scaleY: stagger([0.5, 1]), // Scale from 0.5 to 1 distributed
duration: 800,
})
```
## Scroll Animations
### ScrollObserver
```tsx
'use client'
import { useEffect, useRef } from 'react'
import { animate, createScope } from 'animejs'
export function ScrollReveal() {
const rootRef = useRef<HTMLDivElement>(null)
const scopeRef = useRef<ReturnType<typeof createScope> | null>(null)
useEffect(() => {
scopeRef.current = createScope({ root: rootRef.current }).add(() => {
// Using Intersection Observer pattern
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
animate(entry.target, {
opacity: [0, 1],
translateY: [50, 0],
duration: 800,
ease: 'out(3)',
})
observer.unobserve(entry.target)
}
})
},
{ threshold: 0.2 }
)
document.querySelectorAll('.reveal-item').forEach((el) => {
observer.observe(el)
})
})
return () => scopeRef.current?.revert()
}, [])
return (
<div ref={rootRef}>
{[1, 2, 3, 4].map((i) => (
<div key={i} className="reveal-item opacity-0">
Item {i}
</div>
))}
</div>
)
}
```
## Text Animations
### Character Split Animation
```tsx
'use client'
import { useEffect, useRef } from 'react'
import { animate, createScope, stagger } from 'animejs'
export function TextReveal({ text }: { text: string }) {
const rootRef = useRef<HTMLDivElement>(null)
const scopeRef = useRef<ReturnType<typeof createScope> | null>(null)
useEffect(() => {
scopeRef.current = createScope({ root: rootRef.current }).add(() => {
animate('.char', {
opacity: [0, 1],
translateY: [50, 0],
rotateX: [-90, 0],
delay: stagger(30),
duration: 600,
ease: 'out(3)',
})
})
return () => scopeRef.current?.revert()
}, [])
return (
<div ref={rootRef} className="overflow-hidden">
{text.split('').map((char, i) => (
<span key={i} className="char inline-block opacity-0">
{char === ' ' ? '\u00A0' : char}
</span>
))}
</div>
)
}
```
### Word Animation
```tsx
export function WordReveal({ text }: { text: string }) {
const rootRef = useRef<HTMLDivElement>(null)
const scopeRef = useRef<ReturnType<typeof createScope> | null>(null)
useEffect(() => {
scopeRef.current = createScope({ root: rootRef.current }).add(() => {
animate('.word', {
opacity: [0, 1],
translateY: ['100%', '0%'],
delay: stagger(80),
duration: 800,
ease: 'out(4)',
})
})
return () => scopeRef.current?.revert()
}, [])
return (
<div ref={rootRef}>
{text.split(' ').map((word, i) => (
<span key={i} className="inline-block overflow-hidden mr-2">
<span className="word inline-block opacity-0">{word}</span>
</span>
))}
</div>
)
}
```
## SVG Animations
### Path Drawing
```tsx
import { svg } from 'animejs'
export function DrawSVG() {
const svgRef = useRef<SVGSVGElement>(null)
const scopeRef = useRef<ReturnType<typeof createScope> | null>(null)
useEffect(() => {
scopeRef.current = createScope({ root: svgRef.current }).add(() => {
const drawable = svg.createDrawable('.draw-path')
animate(drawable, {
draw: ['0 0', '0 1'], // From 0% to 100%
duration: 2000,
ease: 'inOut(2)',
})
})
return () => scopeRef.current?.revert()
}, [])
return (
<svg ref={svgRef} viewBox="0 0 100 100">
<path
className="draw-path"
d="M10,50 Q50,10 90,50 T90,90"
fill="none"
stroke="white"
strokeWidth="2"
/>
</svg>
)
}
```
### SVG Morphing
```tsx
import { svg } from 'animejs'
export function MorphSVG() {
const pathRef = useRef<SVGPathElement>(null)
useEffect(() => {
const morph = svg.createMorph(pathRef.current)
animate(morph, {
to: 'M50,10 A40,40 0 1,1 50,90 A40,40 0 1,1 50,10', // Circle
duration: 1000,
ease: 'inOut(2)',
})
}, [])
return (
<svg viewBox="0 0 100 100">
<path ref={pathRef} d="M50,10 L90,90 L10,90 Z" fill="white" />
</svg>
)
}
```
### Motion Path
```tsx
import { svg } from 'animejs'
export function MotionPath() {
const elementRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const motionPath = svg.createMotionPath('#motion-path')
animate(elementRef.current, {
translateX: motionPath.x,
translateY: motionPath.y,
rotate: motionPath.angle,
duration: 3000,
ease: 'linear',
loop: true,
})
}, [])
return (
<>
<svg className="absolute">
<path id="motion-path" d="M0,100 Q250,0 500,100" fill="none" />
</svg>
<div ref={elementRef} className="w-4 h-4 bg-white rounded-full" />
</>
)
}
```
## Draggable
### Basic Draggable
```tsx
import { createDraggable } from 'animejs'
export function DraggableBox() {
const boxRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const draggable = createDraggable(boxRef.current, {
trigger: boxRef.current,
releaseEase: 'out(3)',
releaseStiffness: 50,
})
return () => draggable.revert()
}, [])
return (
<div ref={boxRef} className="w-20 h-20 bg-white cursor-grab">
Drag me
</div>
)
}
```
### Draggable with Constraints
```tsx
createDraggable(element, {
container: containerRef.current, // Constrain to container
x: { min: 0, max: 500 }, // X bounds
y: { min: 0, max: 300 }, // Y bounds
snap: { x: 50, y: 50 }, // Snap to grid
})
```
## Easing Reference (v4)
### Built-in Easings
```tsx
// v4 easing syntax (no 'ease' prefix)
ease: 'linear'
ease: 'in(2)' // Power in
ease: 'out(2)' // Power out (default)
ease: 'inOut(2)' // Power in-out
ease: 'out(4)' // Stronger ease out
```
### Spring Easing
```tsx
import { createSpring } from 'animejs'
const spring = createSpring({
mass: 1,
stiffness: 100,
damping: 10,
velocity: 0,
})
animate('.element', {
translateX: 250,
ease: spring,
})
```
### Custom Easing
```tsx
// Cubic bezier
ease: 'cubicBezier(0.76, 0, 0.24, 1)'
// Custom function
ease: (t) => t * t // Quadratic
```
## v3 to v4 Migration
| v3 | v4 |
|----|----|
| `anime({ targets, ...props })` | `animate(targets, { ...props })` |
| `easing: 'easeOutQuad'` | `ease: 'out(2)'` |
| `easing: 'easeInOutCubic'` | `ease: 'inOut(3)'` |
| `endDelay` | `loopDelay` |
| `direction: 'reverse'` | `reversed: true` |
| `direction: 'alternate'` | `alternate: true` |
| `update` callback | `onUpdate` callback |
| `begin` callback | `onBegin` callback |
| `complete` callback | `onComplete` callback |
| `.finished.then()` | `.then()` |
| `anime.timeline()` | `createTimeline()` |
| `anime.stagger()` | `stagger()` |
## When to Use Anime.js vs GSAP
| Use Anime.js | Use GSAP |
|--------------|----------|
| Simple animations | Complex scroll-driven |
| Lightweight needs (~17kb) | ScrollTrigger required |
| Quick prototypes | Production timelines |
| SVG morphing | SplitText, MorphSVG |
| Draggable elements | Pin sections |
## Sources
- [Anime.js Documentation](https://animejs.com/documentation/)
- [Using with React](https://animejs.com/documentation/getting-started/using-with-react/)
- [v3 to v4 Migration Guide](https://github.com/juliangarnier/anime/wiki/Migrating-from-v3-to-v4)