Use this skill when building web animations with Anime.js v4 — tweens, timelines, stagger effects, scroll-driven animation, SVG line drawing, and draggable interactions.
npm install animejs
Anime.js v4 is fully tree-shakeable. Import only what you need:
import { animate, stagger, createTimeline, createDraggable } from "animejs";
Full bundle: ~24.5 KB minified. Tree-shaking reduces this significantly.
| Function | Purpose |
|---|---|
animate(targets, params) | Animate one or more targets. |
createTimeline(defaults) | Create a timeline to sequence animations. |
stagger(value, options) | Generate staggered delays or values. |
createDraggable(targets, params) | Make elements draggable with snap and inertia. |
import { animate } from "animejs";
animate(".element", {
translateX: 250,
rotate: "1turn",
duration: 800,
ease: "outExpo",
});
translateX, translateY, scale, rotate, skew.opacity, color, backgroundColor, borderRadius.d, cx, cy, r, strokeDashoffset.Built-in easings follow the pattern in, out, inOut plus a curve name:
"outExpo" "inOutQuad" "outElastic(1, 0.5)" "outBounce"
"linear" "inOutSine" "outBack(1.7)" "spring(1, 80, 10, 0)"
Animate through multiple steps:
animate(".box", {
keyframes: [
{ translateX: 100, duration: 400 },
{ translateY: 50, duration: 300 },
{ rotate: "1turn", duration: 500 },
],
ease: "outQuad",
});
Pass a function to generate per-target values:
animate(".dot", {
translateX: (el, i) => 50 + i * 30,
duration: (el, i) => 600 + i * 100,
delay: (el, i) => i * 80,
ease: "outExpo",
});
import { createTimeline } from "animejs";
const tl = createTimeline({
defaults: { duration: 600, ease: "outExpo" },
});
tl.add(".title", { opacity: [0, 1], translateY: [30, 0] })
.add(".subtitle", { opacity: [0, 1], translateY: [20, 0] }, "-=400")
.add(".cta", { scale: [0.8, 1], opacity: [0, 1] }, "-=300");
import { animate, stagger } from "animejs";
animate(".card", {
translateY: [40, 0],
opacity: [0, 1],
duration: 500,
delay: stagger(80), // 80 ms between each card
ease: "outQuad",
});
Grid stagger (radiates from center):
animate(".grid-item", {
scale: [0, 1],
delay: stagger(50, { grid: [10, 10], from: "center" }),
duration: 600,
ease: "outElastic(1, 0.5)",
});
import { animate } from "animejs";
animate(".reveal", {
translateY: [60, 0],
opacity: [0, 1],
duration: 800,
ease: "outQuad",
autoplay: false, // start paused
onScroll: {
target: ".reveal",
enter: "bottom", // when element enters the viewport bottom
},
});
import { animate } from "animejs";
const path = document.querySelector(".draw-path");
const length = path.getTotalLength();
// Set up initial state
path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;
animate(path, {
strokeDashoffset: [length, 0],
duration: 2000,
ease: "inOutSine",
});
import { createDraggable } from "animejs";
createDraggable(".draggable", {
container: ".bounds",
snap: { x: 50, y: 50 }, // snap to 50px grid
ease: "outExpo",
releaseEase: "outElastic(1, 0.5)",
});
import { animate } from "animejs";
const progress = { value: 0 };
animate(progress, {
value: 100,
duration: 1500,
ease: "linear",
onUpdate: () => {
document.querySelector(".counter").textContent = Math.round(progress.value);
},
});
Use createScope to scope animations to a component and clean up on unmount:
import { useRef, useEffect } from "react";
import { animate, createScope } from "animejs";
function AnimatedList({ items }) {
const root = useRef(null);
const scope = useRef(null);
useEffect(() => {
scope.current = createScope({ root: root.current }).add(() => {
animate(".item", {
translateY: [20, 0],
opacity: [0, 1],
delay: (el, i) => i * 60,
duration: 500,
ease: "outQuad",
});
});
return () => scope.current.revert();
}, []);
return (
<ul ref={root}>
{items.map((item) => (
<li key={item.id} className="item">
{item.label}
</li>
))}
</ul>
);
}
Every animate() call returns a controller:
const anim = animate(".box", {
translateX: 300,
duration: 1000,
autoplay: false,
});
anim.play();
anim.pause();
anim.reverse();
anim.restart();
anim.seek(500); // jump to 500 ms
| Pitfall | Fix |
|---|---|
| Importing from the wrong package | Use import { animate } from "animejs" (v4). Do not use import anime from "animejs" — that is the v3 default export. |
| Not cleaning up in SPAs | Use createScope and call scope.revert() on component unmount to kill active animations and restore initial values. |
| Animating layout properties | Prefer translateX/translateY over left/top for GPU-composited rendering. |
Stagger with no from on grids | Without from, a grid stagger animates left-to-right only. Set from: "center" or from: [row, col] for radial effects. |
Forgetting autoplay: false | Scroll-driven and manually-triggered animations should start paused — otherwise they fire immediately on creation. |
| Using v3 syntax in v4 | v4 replaces anime() with animate(), anime.timeline() with createTimeline(), and anime.stagger() with stagger(). |
animate, stagger, etc. — not the entire library — to minimize bundle size.createScope in component frameworks. It auto-tracks animations and provides a clean revert() method for teardown.translateX, translateY, scale, rotate are GPU-composited and avoid layout thrashing.createTimeline({ defaults: {} }) to keep individual .add() calls short and readable.stagger() instead of manual delay math. It handles grids, radial patterns, and eased distributions out of the box.const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduced) return; // skip or simplify animation
animate() to pause, reverse, or seek programmatically.animate() calls.