animejs
Anime.js v4 Animation Skill
Anime.js v4 is a modular JavaScript animation engine (10KB full / 3KB WAAPI-only). This skill ensures you write correct v4 syntax — the API changed significantly from v3.
Installation
npm install animejs
CDN (ESM):
<script type="module">
import { animate } from 'https://cdn.jsdelivr.net/npm/animejs/+esm';
</script>
Core Concept: Named Imports
v4 uses named exports, not a default anime object. Import only what you need:
import { animate, stagger, createTimeline, utils } from 'animejs';
Subpath imports for smaller bundles:
import { animate } from 'animejs/animation';
import { createTimeline } from 'animejs/timeline';
import { createDraggable } from 'animejs/draggable';
import { onScroll } from 'animejs/events';
import { stagger, utils } from 'animejs/utils';
import { waapi } from 'animejs/waapi';
Quick Reference: animate()
animate(targets, parameters);
Targets: CSS selectors, DOM elements, NodeLists, JS objects, or arrays of these.
Parameters combine animatable properties + playback settings + callbacks:
animate('.box', {
translateX: 250, // CSS transform
opacity: [0, 1], // [from, to]
scale: { from: 0.5, to: 1 },
backgroundColor: '#FF0000', // color animation
duration: 800,
delay: stagger(100), // stagger per element
ease: 'outExpo', // easing function
loop: 2, // repeat 2 times (3 total plays)
alternate: true, // ping-pong
onComplete: () => console.log('done'),
});
Animatable Properties
| Category | Examples |
|---|---|
| CSS | opacity, width, height, backgroundColor, borderRadius |
| Transforms | translateX, translateY, rotate, scale, skewX |
| CSS Variables | '--my-var': 100 |
| SVG Attributes | cx, r, points, d (via morphTo) |
| HTML Attributes | value, data-count |
| JS Object Props | Any numeric property on a plain object |
Value Formats
translateX: 250, // unitless (px for transforms)
translateX: '10rem', // with unit
translateX: '+=100', // relative (add 100)
translateX: '-=50px', // relative subtract
opacity: [0, 1], // [from, to] shorthand
scale: { from: 0.5, to: 1.5 }, // explicit from/to
rotate: '1turn', // CSS units work
Function-Based Values
animate('.item', {
translateX: (el, i, total) => i * 50, // per-element
delay: (el, i) => i * 100,
});
Keyframes (Per-Property)
animate('.ball', {
y: [
{ to: '-2.75rem', ease: 'outExpo', duration: 600 },
{ to: 0, ease: 'outBounce', duration: 800, delay: 100 },
],
});
Playback Settings
| Setting | Default | Description |
|---|---|---|
duration |
1000 | ms |
delay |
0 | ms before start |
loop |
0 | repetitions (0 = play once, true = infinite) |
loopDelay |
0 | ms between loops |
alternate |
false | reverse on each loop |
reversed |
false | play backward |
autoplay |
true | start immediately |
playbackRate |
1 | speed multiplier |
frameRate |
null | cap FPS |
Callbacks
animate(el, {
translateX: 200,
onBegin: (anim) => {}, // after delay, when animation starts
onUpdate: (anim) => {}, // every frame
onRender: (anim) => {}, // when properties are applied to DOM
onLoop: (anim) => {}, // at each loop boundary
onPause: (anim) => {}, // when paused
onComplete: (anim) => {}, // when fully done
});
// Promise-based
animate(el, { x: 100 }).then(() => console.log('done'));
Playback Methods
const anim = animate(el, { x: 200 });
anim.play(); // play forward
anim.pause();
anim.resume(); // continue current direction
anim.reverse(); // play backward
anim.restart();
anim.seek(500); // jump to 500ms
anim.complete(); // skip to end
anim.cancel(); // stop and reset
anim.revert(); // remove all changes
Easing
v4 uses ease (not easing). Names are shortened (no ease prefix):
ease: 'outQuad' // was easeOutQuad in v3
ease: 'inOutExpo' // was easeInOutExpo
ease: 'linear'
ease: 'out(3)' // parametric: power of 3
ease: 'inOut(2)' // parametric in-out
All built-in easings: linear, in, out, inOut, outIn, inQuad, outQuad, inOutQuad, inCubic, outCubic, inOutCubic, inQuart, outQuart, inOutQuart, inQuint, outQuint, inOutQuint, inSine, outSine, inOutSine, inCirc, outCirc, inOutCirc, inExpo, outExpo, inOutExpo, inBounce, outBounce, inOutBounce, inBack, outBack, inOutBack, inElastic, outElastic, inOutElastic.
Cubic Bezier
import { cubicBezier } from 'animejs';
ease: cubicBezier(0.7, 0.1, 0.5, 0.9)
Spring Physics
import { createSpring } from 'animejs';
ease: createSpring({ mass: 1, stiffness: 100, damping: 10, velocity: 0 })
Presets: spring() (default), spring({ stiffness: 200, damping: 8 }) (snappy), spring({ stiffness: 80, damping: 5 }) (bouncy).
Steps
ease: 'steps(5)' // 5 discrete steps
Custom Easing Function
ease: t => 1 - Math.sqrt(1 - t * t) // direct function, no wrapper
Stagger
import { stagger } from 'animejs';
delay: stagger(100) // 0, 100, 200, 300...
delay: stagger(100, { start: 500 }) // 500, 600, 700...
delay: stagger(100, { from: 'center' }) // from center outward
delay: stagger(100, { from: 'last' }) // from last element
delay: stagger(100, { reversed: true }) // reverse order
delay: stagger([0, 200]) // distribute 0-200 across elements
delay: stagger(100, { grid: [14, 5] }) // 2D grid stagger
delay: stagger(100, { grid: [14, 5], from: 'center', ease: 'inQuad' })
Timeline
import { createTimeline } from 'animejs';
const tl = createTimeline({
defaults: { duration: 600, ease: 'outExpo' },
loop: true,
alternate: true,
});
tl.add('.box-1', { translateX: 250 }) // starts at 0
.add('.box-2', { translateX: 250 }, 200) // starts at 200ms
.add('.box-3', { translateX: 250 }, '+=100') // 100ms after previous ends
.add('.box-4', { translateX: 250 }, '-=300') // 300ms before previous ends
.add('.box-5', { translateX: 250 }, '<') // same start as previous
.add('.box-6', { translateX: 250 }, '<+=200'); // 200ms after previous starts
Labels
tl.label('intro')
.add('.title', { opacity: [0, 1] })
.label('content', '+=500')
.add('.body', { opacity: [0, 1] }, 'content');
Timeline Callbacks
tl.call(() => console.log('midpoint'), 1500); // at 1500ms
Scroll-Linked Animations
import { animate, onScroll } from 'animejs';
// Trigger on scroll into view
animate('.card', {
opacity: [0, 1],
translateY: [50, 0],
autoplay: onScroll({
target: '.card', // element to observe
enter: 'bottom', // trigger when entering viewport bottom
leave: 'top', // stop when leaving viewport top
}),
});
// Sync animation progress to scroll position
animate('.progress-bar', {
scaleX: [0, 1],
autoplay: onScroll({
target: '.section',
sync: 'playback', // link progress to scroll %
}),
});
SVG Utilities
import { animate, svg } from 'animejs';
// Line drawing
const drawable = svg.createDrawable('path.line');
animate(drawable, { draw: '0 1', duration: 1500 });
// SVG morphing
animate('path#shape', {
d: svg.morphTo('path#target-shape'),
duration: 1000,
});
// Motion path
const path = svg.createMotionPath('path#track');
animate('.mover', {
...path, // spreads translateX, translateY, rotate
duration: 2000,
});
Draggable
import { createDraggable } from 'animejs';
const drag = createDraggable('.card', {
container: '.bounds',
releaseEase: createSpring({ stiffness: 100, damping: 15 }),
cursor: { onHover: 'grab', onGrab: 'grabbing' },
snap: { x: 50, y: 50 },
onDrag: (draggable) => {},
onRelease: (draggable) => {},
onSettle: (draggable) => {},
});
Animatable (High-Frequency Updates)
For values that change every frame (mouse tracking, game loops), use createAnimatable instead of calling animate() repeatedly:
import { createAnimatable, utils } from 'animejs';
const box = createAnimatable('.box', {
x: 500, // duration for x transitions
y: 500,
ease: 'out(3)',
});
window.addEventListener('mousemove', (e) => {
box.x(utils.clamp(e.clientX - cx, -limit, limit));
box.y(utils.clamp(e.clientY - cy, -limit, limit));
});
Text Animation
import { animate, splitText, stagger } from 'animejs';
const { chars, words, lines } = splitText('h1', {
chars: true,
words: true,
lines: true,
});
animate(chars, {
opacity: [0, 1],
translateY: ['1em', 0],
delay: stagger(30),
ease: 'outExpo',
});
Layout Animations
import { createLayout, stagger } from 'animejs';
const layout = createLayout('.grid-container');
layout.update(({ root }) => {
root.classList.toggle('column-layout');
}, {
duration: 800,
delay: stagger(50),
ease: 'outExpo',
});
WAAPI (Lightweight 3KB Alternative)
For simpler animations that don't need JS engine features, use the WAAPI wrapper for hardware-accelerated performance:
import { waapi, stagger, splitText } from 'animejs';
waapi.animate('.box', {
translate: '0 -2rem',
opacity: [0, 1],
delay: stagger(100),
duration: 600,
ease: 'inOut(2)',
});
Utilities
import { utils } from 'animejs';
utils.get(el, 'translateX'); // get current value
utils.set(el, { opacity: 0.5 }); // set without animation
utils.remove(el); // remove from running animations
utils.random(50, 100); // random number in range
utils.clamp(val, min, max);
utils.lerp(start, end, amount);
utils.mapRange(inLow, inHigh, outLow, outHigh, val);
utils.snap(val, increment);
utils.round(val, decimals);
utils.wrap(val, min, max);
React Integration
import { useRef, useEffect } from 'react';
import { createScope, animate, stagger } from 'animejs';
function AnimatedList({ items }) {
const root = useRef(null);
const scope = useRef(null);
useEffect(() => {
scope.current = createScope({ root: root.current })
.add(() => {
animate('.item', {
opacity: [0, 1],
translateY: [20, 0],
delay: stagger(50),
ease: 'outExpo',
});
});
return () => scope.current.revert();
}, []);
return (
<ul ref={root}>
{items.map(item => <li key={item.id} className="item">{item.name}</li>)}
</ul>
);
}
createScope scopes CSS selectors to the root element and provides batch revert() on cleanup — essential for React's strict mode and unmount lifecycle.
Animation Composition
v4 handles overlapping animations on the same property:
// Default: 'replace' — new animation cuts the old one
animate(el, { x: 100 });
animate(el, { x: 200 }); // first is replaced
// Additive: values stack
animate(el, { x: 100, composition: 'add' });
animate(el, { x: 50, composition: 'add' }); // total: 150
// None: v3 behavior, animations overlap
animate(el, { x: 100, composition: 'none' });
Common Pitfalls
easenoteasing— v4 renamed itloop: 1means repeat once (2 total plays), not "play once" like v3- No default
animeobject — use named imports directionis gone — usealternate: trueandreversed: true- Easing names dropped
easeprefix —outQuadnoteaseOutQuad - Spring syntax changed — use
createSpring({...})not'spring(1,80,10,0)' - SVG helpers moved —
svg.createMotionPath()notanime.path() onRenderreplaceschange,onLoopreplacesloopBegin/loopCompleteplay()always goes forward — useresume()to continue previous direction
Reference Files
For detailed API signatures and parameters, read references/api-reference.md.
For common animation recipes and patterns, read references/examples.md.