When writing JavaScript animations using anime.js v4, including DOM element animations, timelines, stagger effects, scroll-triggered animations, SVG animations, text animations, draggable elements, layout transitions, or any motion/transition effects on websites. Also use when the user says "animate," "animation library," "anime.js," "stagger," "timeline animation," "scroll animation," "text animation," "draggable," or "motion effects."
Resources
1Install
npx skillscat add almeidamarcell/claude-code-skills/animejs Install via the SkillsCat registry.
anime.js v4 Animation
You are an expert in anime.js v4 (latest: v4.3.5), the lightweight JavaScript animation library. Generate production-ready animation code using the v4 API exclusively. Never use v3 syntax.
CRITICAL: v4 API Only
anime.js v4 introduced breaking changes from v3. ALWAYS use v4 syntax:
| NEVER use (v3) | ALWAYS use (v4) |
|---|---|
anime({ targets: '.el', ... }) |
animate('.el', { ... }) |
anime.timeline() |
createTimeline() |
anime.stagger() |
stagger() |
easing: 'easeOutQuad' |
ease: 'outQuad' |
begin: fn |
onBegin: fn |
change: fn |
onUpdate: fn |
complete: fn |
onComplete: fn |
direction: 'alternate' |
alternate: true |
direction: 'reverse' |
reversed: true |
endDelay |
loopDelay |
anime.path() |
svg.createMotionPath() |
anime.setDashoffset() |
svg.createDrawable() |
animation.finished.then() |
animation.then() |
Installation & Setup
CDN:
<script src="https://cdn.jsdelivr.net/npm/animejs@4/lib/anime.iife.min.js"></script>npm:
npm install animejsES Module imports (recommended):
import { animate, stagger, createTimeline } from 'animejs';
// Additional features — import only what you need:
import { createAnimatable } from 'animejs';
import { createDraggable } from 'animejs';
import { createLayout } from 'animejs';
import { onScroll } from 'animejs';
import { waapi } from 'animejs';
import { svg } from 'animejs';
import { splitText } from 'animejs';
import { engine, utils } from 'animejs';Subpath imports (tree-shaking):
import { animate } from 'animejs/animation';
import { createTimeline } from 'animejs/timeline';
import { createDraggable } from 'animejs/draggable';
import { onScroll } from 'animejs/events';
import { splitText } from 'animejs/text';Core: animate(targets, parameters)
Targets
// CSS selector
animate('.box', { ... });
// DOM element
animate(document.querySelector('#hero'), { ... });
// Multiple elements (NodeList, Array)
animate(document.querySelectorAll('.card'), { ... });
// JavaScript object (animate numeric properties)
const obj = { progress: 0 };
animate(obj, { progress: 100, onUpdate: () => console.log(obj.progress) });Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
duration |
number | 1000 | Milliseconds |
delay |
number/function | 0 | Delay before start |
ease |
string/function | 'outQuad' |
Easing function |
loop |
number/boolean | false |
true = infinite, number = repeat count |
loopDelay |
number | 0 | Delay between loops |
alternate |
boolean | false |
Reverse direction each loop |
reversed |
boolean | false |
Play backwards |
autoplay |
boolean | true |
Start immediately |
frameRate |
number | - | Limit FPS |
playbackRate |
number | 1 | Speed multiplier (2 = double speed) |
composition |
string | 'replace' |
'replace', 'add', 'blend' |
modifier |
function | - | Transform animated value before applying |
Callbacks
animate('.el', {
translateX: 250,
onBegin: (anim) => {}, // When animation starts (after delay)
onUpdate: (anim) => {}, // Every frame
onComplete: (anim) => {}, // When animation finishes
onLoop: (anim) => {}, // After each loop iteration
onPause: (anim) => {}, // When paused
}).then((anim) => {}); // Promise-based completionPlayback Controls
const anim = animate('.el', { translateX: 250, autoplay: false });
anim.play(); // Play forward
anim.pause(); // Pause
anim.resume(); // Resume from current position and direction
anim.reverse(); // Reverse direction
anim.restart(); // Restart from beginning
anim.seek(500); // Jump to 500ms
anim.reset(); // Reset to initial values
anim.complete(); // Jump to end
anim.cancel(); // Cancel and clean up
anim.revert(); // Revert targets to original valuesAnimatable Properties
animate('.el', {
// CSS transforms
translateX: 250,
translateY: 100,
rotate: '1turn',
scale: 1.5,
skewX: '15deg',
// CSS properties
opacity: [0, 1],
backgroundColor: '#FF0000',
borderRadius: '50%',
width: '200px',
// CSS variables
'--custom-prop': 100,
// Relative values
translateX: '+=100',
rotate: '-=45deg',
// From-to syntax
opacity: { from: 0, to: 1 },
translateY: [50, 0], // [from, to]
// Per-property parameters
translateX: { to: 250, duration: 800, ease: 'outExpo' },
opacity: { to: 1, duration: 400, delay: 200 },
// Function-based values (per target)
translateX: (target, index, total) => index * 50,
delay: (target, index) => index * 100,
});Keyframes
Property-level (array of values):
animate('.el', {
translateX: [0, 100, 50, 200],
duration: 2000,
});Property-level (array of objects):
animate('.el', {
translateX: [
{ to: 100, duration: 500, ease: 'outQuad' },
{ to: 50, duration: 300 },
{ to: 200, duration: 700, ease: 'inOutCubic' },
],
});Percentage-based:
animate('.el', {
keyframes: {
'0%': { translateX: 0, opacity: 0 },
'40%': { translateX: 100, opacity: 1 },
'100%': { translateX: 250, opacity: 0.5 },
},
duration: 2000,
});Stagger
Distribute values or delays across multiple targets.
import { animate, stagger } from 'animejs';
// Basic delay stagger: each element starts 100ms after the previous
animate('.card', {
opacity: [0, 1],
translateY: [30, 0],
delay: stagger(100),
duration: 500,
ease: 'outCubic',
});
// From center outward
animate('.item', { scale: [0, 1], delay: stagger(80, { from: 'center' }) });
// From edges inward
animate('.item', { scale: [0, 1], delay: stagger(80, { from: 'edges' }) });
// From specific index
animate('.item', { scale: [0, 1], delay: stagger(80, { from: 3 }) });
// Range distribution: values spread evenly between 0 and 200
animate('.item', { translateX: stagger([0, 200]) });
// Grid stagger (rows x cols)
animate('.grid-item', {
scale: [0, 1],
delay: stagger(50, { grid: [4, 6], from: 'center', axis: 'y' }),
});
// Eased stagger distribution
animate('.item', { opacity: [0, 1], delay: stagger(300, { ease: 'inOutQuad' }) });Timelines
Sequence and synchronize multiple animations.
import { createTimeline, stagger } from 'animejs';
const tl = createTimeline({
defaults: { duration: 600, ease: 'outQuad' },
});
// Animations chain sequentially by default
tl.add('.header', { opacity: [0, 1], translateY: [-30, 0] })
.add('.subtitle', { opacity: [0, 1], translateY: [20, 0] }, '-=300') // Overlap by 300ms
.add('.cta-btn', { opacity: [0, 1], scale: [0.8, 1] }, '+=100') // 100ms gap
.add('.cards', {
opacity: [0, 1],
translateY: [40, 0],
delay: stagger(80),
}, '-=200');
// Absolute positioning (ms from timeline start)
tl.add('.bg', { opacity: [0, 0.5] }, 0); // Starts at 0ms
// Timeline controls
tl.play();
tl.pause();
tl.seek(1000);
tl.reverse();
tl.restart();Text Splitting
Split text into lines, words, or characters for per-element animation.
import { splitText, animate, stagger } from 'animejs';
// Split heading into characters
const splitter = splitText('.hero-title', {
chars: true, // Split into characters
// words: true, // Split into words
// lines: true, // Split into lines
accessible: true, // Maintain screen reader accessibility
});
// Animate each character
animate(splitter.chars, {
opacity: [0, 1],
translateY: [20, 0],
delay: stagger(30),
duration: 400,
ease: 'outCubic',
});
// Revert DOM to original text when done
// splitter.revert();Scroll Observer
Trigger and synchronize animations with scroll position.
import { animate, onScroll } from 'animejs';
// Trigger animation when element enters viewport
onScroll('.section', {
onEnter: () => {
animate('.section .content', {
opacity: [0, 1],
translateY: [40, 0],
duration: 800,
ease: 'outQuad',
});
},
});
// Directional callbacks
onScroll('.element', {
onEnterForward: () => {}, // Scrolling down into view
onEnterBackward: () => {}, // Scrolling up into view
onLeaveForward: () => {}, // Scrolling down out of view
onLeaveBackward: () => {}, // Scrolling up out of view
});
// Sync animation progress to scroll position
const anim = animate('.parallax-bg', {
translateY: [-100, 100],
autoplay: false,
});
onScroll('.parallax-section', {
sync: 'playback', // Links scroll position to animation progress
link: anim,
});
// Thresholds
onScroll('.el', {
enter: 'top bottom', // When top of el crosses bottom of viewport
leave: 'bottom top', // When bottom of el crosses top of viewport
});WAAPI (Web Animation API)
Lightweight 3KB alternative using browser-native Web Animation API. Hardware-accelerated. Use for simple animations where performance is critical.
import { waapi } from 'animejs';
const anim = waapi.animate('.el', {
translateX: 250,
opacity: [0, 1],
duration: 600,
ease: 'outQuad',
});When to use WAAPI vs full animate():
- WAAPI: Simple CSS transforms/opacity, high-performance needs, many simultaneous elements
- Full
animate(): JS object animation, complex keyframes,onUpdatecallback, modifier functions, SVG features
Animatable
Reactive value animation — ideal for cursor tracking, interactive UIs, and frequently changing values.
import { createAnimatable } from 'animejs';
const follower = createAnimatable('.cursor-follower', {
x: { duration: 300, ease: 'outQuad' },
y: { duration: 300, ease: 'outQuad' },
});
document.addEventListener('mousemove', (e) => {
follower.x(e.clientX); // Setter: animates to value
follower.y(e.clientY);
});
// Getter
const currentX = follower.x();Layout Animations
Animate layout changes that are normally impossible (CSS display, grid, flex, DOM order).
import { createLayout } from 'animejs';
const layout = createLayout('.container', {
duration: 500,
ease: 'outQuad',
// Enter/exit states for added/removed elements
enterFrom: { opacity: 0, scale: 0.8 },
leaveTo: { opacity: 0, scale: 0.8 },
});
// Animate any layout change
layout.update(() => {
// Toggle class, change display, reorder DOM, etc.
container.classList.toggle('grid-view');
});
// Or manual record → animate pattern
layout.record();
// ... make DOM changes ...
layout.animate();Draggable
Add drag interaction with physics.
import { createDraggable } from 'animejs';
const draggable = createDraggable('.card', {
x: { snap: 100 }, // Snap to 100px grid on x-axis
y: { snap: 100 },
friction: 0.5, // Drag friction
mass: 1, // Physics mass
stiffness: 100, // Spring stiffness
damping: 10, // Spring damping
onGrab: (self) => {},
onDrag: (self) => {},
onRelease: (self) => {},
onSnap: (self) => {},
onSettle: (self) => {},
});
// Constrain to container
const draggable = createDraggable('.el', {
container: '.bounds',
x: true,
y: true,
});SVG Features
import { animate, svg } from 'animejs';
// Line drawing (animate stroke)
const drawable = svg.createDrawable('.svg-path');
animate(drawable, { draw: [0, 1], duration: 1500, ease: 'inOutQuad' });
// Shape morphing
animate('.shape path', {
d: svg.morphTo('.target-shape path'),
duration: 1000,
ease: 'inOutQuad',
});
// Motion path (move element along SVG path)
const motionPath = svg.createMotionPath('.motion-path');
animate('.moving-element', {
...motionPath, // Spread the path data
duration: 3000,
ease: 'linear',
loop: true,
});Easings
| Family | In | Out | InOut |
|---|---|---|---|
| Quad | inQuad |
outQuad |
inOutQuad |
| Cubic | inCubic |
outCubic |
inOutCubic |
| Quart | inQuart |
outQuart |
inOutQuart |
| Quint | inQuint |
outQuint |
inOutQuint |
| Sine | inSine |
outSine |
inOutSine |
| Expo | inExpo |
outExpo |
inOutExpo |
| Circ | inCirc |
outCirc |
inOutCirc |
| Back | inBack |
outBack |
inOutBack |
| Elastic | inElastic |
outElastic |
inOutElastic |
Custom easings:
ease: 'cubicBezier(0.25, 0.1, 0.25, 1.0)' // CSS cubic-bezier
ease: 'spring(1, 100, 10, 0)' // spring(mass, stiffness, damping, velocity)
ease: 'steps(5)' // Stepped animation
ease: 'linear' // Constant speedWhen to use which:
- Entrances:
outQuad,outCubic,outExpo(fast start, slow end) - Exits:
inQuad,inCubic(slow start, fast end) - State changes:
inOutQuad,inOutCubic(smooth both ends) - Bouncy/playful:
outBack,outElastic,spring() - Continuous/looping:
linear
For the complete easing catalog with motion descriptions and presets, see references/easing-reference.md.
Engine Configuration
import { engine } from 'animejs';
engine.timeUnit = 'ms'; // 'ms' or 's'
engine.speed = 1; // Global speed multiplier
engine.fps = 60; // Max frames per second
engine.precision = 4; // Decimal precision
engine.pauseOnDocumentHidden = true; // Pause when tab hiddenUtilities
import { utils } from 'animejs';
utils.get('.el', 'translateX'); // Get current value
utils.set('.el', { opacity: 0.5 }); // Set without animating
utils.random(0, 100); // Random number
utils.clamp(value, 0, 100); // Clamp to range
utils.snap(value, 10); // Snap to nearest 10
utils.mapRange(value, 0, 1, 0, 100); // Remap range
utils.lerp(start, end, 0.5); // Linear interpolationSelector shorthand:
import { $ } from 'animejs';
const elements = $('.card'); // Like querySelectorAllCommon Patterns
Fade in on load
animate('.element', {
opacity: [0, 1],
translateY: [20, 0],
duration: 600,
ease: 'outQuad',
});Staggered list entrance
animate('.list-item', {
opacity: [0, 1],
translateY: [30, 0],
delay: stagger(80),
duration: 500,
ease: 'outCubic',
});Hover scale effect
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('mouseenter', () =>
animate(btn, { scale: 1.05, duration: 200, ease: 'outQuad' })
);
btn.addEventListener('mouseleave', () =>
animate(btn, { scale: 1, duration: 200, ease: 'outQuad' })
);
});Loading spinner
animate('.spinner', {
rotate: '1turn',
duration: 800,
loop: true,
ease: 'linear',
});Animated counter
const counter = { value: 0 };
animate(counter, {
value: 2500,
duration: 2000,
ease: 'outExpo',
onUpdate: () => {
document.querySelector('.count').textContent = Math.round(counter.value);
},
});Scroll-triggered reveal
onScroll('.reveal-section', {
onEnter: () => {
animate('.reveal-section .item', {
opacity: [0, 1],
translateY: [40, 0],
delay: stagger(100),
duration: 600,
ease: 'outCubic',
});
},
});Text reveal
const splitter = splitText('.headline', { chars: true, accessible: true });
animate(splitter.chars, {
opacity: [0, 1],
translateY: [20, 0],
delay: stagger(25),
duration: 400,
ease: 'outCubic',
});Draggable card
createDraggable('.drag-card', {
container: '.card-area',
x: true,
y: true,
friction: 0.7,
onRelease: (self) => {
// Snap back or process drop
},
});For complete website animation patterns (hero sections, navigation effects, parallax, modals, carousels, etc.), see references/website-animation-patterns.md.
v3 to v4 Complete Migration Reference
| v3 (NEVER use) | v4 (ALWAYS use) |
|---|---|
anime({ targets: '.el', ... }) |
animate('.el', { ... }) |
anime.timeline({ ... }) |
createTimeline({ ... }) |
anime.stagger(100) |
stagger(100) |
easing: 'easeOutQuad' |
ease: 'outQuad' |
easing: 'easeInOutExpo' |
ease: 'inOutExpo' |
begin: fn |
onBegin: fn |
change: fn |
onUpdate: fn |
complete: fn |
onComplete: fn |
changeBegin: fn |
Removed — use onBegin |
changeComplete: fn |
Removed — use onComplete |
direction: 'alternate' |
alternate: true |
direction: 'reverse' |
reversed: true |
endDelay: 500 |
loopDelay: 500 |
anime.remove(targets) |
anim.revert() |
anime.path('.path') |
svg.createMotionPath('.path') |
anime.setDashoffset(el) |
svg.createDrawable(el) |
animation.finished.then(fn) |
animation.then(fn) |
value: 100 (in keyframe objects) |
to: 100 |
import anime from 'animejs' |
import { animate } from 'animejs' |