EmpireUI
Get Pro
← Blog9 min read#anime.js#animation#javascript

Anime.js v4 in 2026: Timeline, Stagger and SVG Animations

Anime.js v4 rewrote the timeline and stagger APIs completely. Here's how to actually use them in 2026 — with real SVG, scroll, and sequencing examples.

abstract colorful motion blur graphic representing javascript animation

What Actually Changed in Anime.js v4

Anime.js v4 — dropped in late 2024 and now the stable baseline as of mid-2026 — wasn't an incremental update. The author rewrote the internals almost entirely, which means a lot of v3 muscle memory gets you into trouble fast. If you were calling anime({ targets, duration, easing }) and expecting v3 behavior, you're going to hit walls.

The biggest structural shift is that anime() no longer returns a plain animation object. It returns an Animation instance with a proper promise-based API and lifecycle hooks that actually work predictably. In v3, chaining .finished was technically available but felt bolted on. Now it's the intended pattern. You get .play(), .pause(), .reverse(), .seek() — all chainable, all synchronous-safe.

Timeline got a complete redesign too. The old anime.timeline() is gone. You now create timelines with createTimeline() from the named export. This breaks every tutorial written before 2025. Worth noting: the npm package itself is still animejs, so installs are the same — it's the import surface that changed.

Honestly, the v4 breaking changes were worth it. The v3 API had accumulated enough inconsistency that large animation sequences required workarounds that felt like fighting the library. v4 is opinionated in ways that make complex choreography readable rather than a nested callback nightmare.

Installing and Importing v4 Correctly

Start here before anything else, because the import story tripped up a lot of people at launch. npm install animejs gives you v4 as of 2026. The package ships ESM and CJS builds, so you're covered whether you're in Next.js App Router or a vanilla Vite setup.

npm install animejs

In ESM environments — which is everything in 2026, really — you import named exports: ``js import { animate, createTimeline, stagger, svg } from 'animejs'; ` That's it. No default export to remember. No anime.default weirdness. If you're seeing anime is not a function`, you're loading a UMD shim somewhere and it's conflicting with the ESM build.

Quick aside: TypeScript types ship with the package now. You don't need @types/animejs. The v3-era DefinitelyTyped types are outdated and will give you wrong intellisense — remove them if you installed them previously.

One more thing — the CDN path changed too. If you're loading from a CDN for prototyping, the correct path is animejs/dist/anime.esm.js, not the old flat anime.min.js. Get that wrong and you'll silently load nothing in a module context.

Building Timelines That Don't Collapse Under Complexity

The timeline API is where v4 earns its rewrite. createTimeline() returns a timeline object you can add animations to with .add(), and each call to .add() takes an element (or selector or NodeList), animation properties, and an optional time offset. That offset system is the key to making complex sequences readable.

import { createTimeline, stagger } from 'animejs';

const tl = createTimeline({ defaults: { duration: 600, ease: 'outExpo' } });

tl
  .add('.hero-title', { opacity: [0, 1], translateY: [40, 0] })
  .add('.hero-subtitle', { opacity: [0, 1], translateY: [20, 0] }, '+=100')
  .add('.cta-button', { scale: [0.8, 1], opacity: [0, 1] }, '+=200');

The offset string '+=100' means 100ms after the previous animation ends. '-=200' overlaps by 200ms. A bare number like 800 is an absolute timestamp from timeline start. This three-mode system covers 95% of real-world sequencing needs without you having to do math on durations.

In practice, I reach for absolute timestamps when I know the visual rhythm in advance (like a 3-beat stagger at 0, 333, 666ms), and relative offsets when I'm building sequences where individual clip durations might change. Mixing them in one timeline is fine — the engine handles it correctly.

The defaults object on createTimeline() is genuinely useful. Any property set there cascades to every .add() call unless overridden. Set your common ease and duration once, then only override where you need something different. Compare that to v3 where you'd repeat easing: 'easeOutExpo' on every single animation.

Stagger: The API You'll Use on Every Project

Stagger in v4 is a function, not a string shorthand. You call stagger(value, options) and it returns a function that anime resolves per-element at runtime. This matters because it composes cleanly — you can stagger delay, duration, start angle, and more all from one call.

import { animate, stagger } from 'animejs';

animate('.grid-item', {
  opacity: [0, 1],
  translateY: [30, 0],
  delay: stagger(80),             // 0ms, 80ms, 160ms, 240ms...
  duration: stagger(400, { start: 600 }), // 600, 680, 760...
  ease: 'outQuart',
});

The start option offsets the stagger base value — useful when you want all elements to animate but the first one shouldn't start at 0. The from option is powerful: pass 'center' and the stagger radiates outward from the middle element, or pass 'last' to reverse order. Pass a specific index for arbitrary epicenters.

animate('.card', {
  scale: [0.9, 1],
  opacity: [0, 1],
  delay: stagger(60, { from: 'center' }),
});

For grid layouts, stagger also accepts a 2D grid option — stagger(100, { grid: [4, 3], from: 'center' }) — which calculates stagger delays based on distance from the epicenter across rows and columns. This produces that ripple-through-a-grid effect that used to require a custom utility function. Now it's one argument. Look, that alone justifies updating from v3.

SVG Animations: Paths, Morphing, and Draw

Anime.js has always had solid SVG support, and v4 formalizes it under the svg named export. The three main features are path following, stroke draw, and morph. All three work without external plugins in 2026.

Stroke draw is the most-used SVG trick — animating a line or shape appearing like it's being drawn. In v4 you use the svg.createDrawable() helper to prepare the element, then animate the draw property: ``js import { animate, svg } from 'animejs'; const path = document.querySelector('#my-path'); svg.createDrawable(path); animate(path, { draw: '0 1', // from 0% to 100% of path length duration: 1200, ease: 'inOutSine', }); ``

That draw property takes a string 'start end' where both values are 0–1 fractions of total path length. Animating from '0 0' to '0 1' draws forward. Animating from '1 1' to '0 1' draws backward. You can also animate the window — '0.2 0.6' to '0.4 0.8' — to create a trailing-paint effect that's subtle but impressive at 48px stroke widths.

Path following lets you move any element along an SVG path using svg.createMotionPath(). The returned object gives you x, y, and angle properties you can feed directly to animate(). This is how you get UI elements that follow a curve without writing a single trigonometry line.

``js const motionPath = svg.createMotionPath('#orbit-path'); animate('.satellite', { ...motionPath, duration: 3000, loop: true, ease: 'linear', }); ` One caveat: the SVG path must be in the same document coordinate space as the element you're moving. If your path is inside a nested <svg> with its own viewBox`, you'll need to account for the transform. The library doesn't auto-correct for that in v4.0 — it may in a future minor.

Scroll-Driven and On-Enter Animations

Anime.js v4 doesn't ship a built-in scroll observer (that's left to the browser's Intersection Observer or the native CSS animation-timeline), but it integrates cleanly with both. The pattern that works best in production is creating a paused animation and calling .play() inside an IntersectionObserver callback.

import { animate } from 'animejs';

const anim = animate('.feature-card', {
  opacity: [0, 1],
  translateX: [-24, 0],
  delay: stagger(100),
  autoplay: false,
});

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        anim.play();
        observer.disconnect();
      }
    });
  },
  { threshold: 0.2 }
);

observer.observe(document.querySelector('.feature-section'));

The autoplay: false option on animate() is new in v4 and much cleaner than the v3 pattern of calling .pause() immediately after creation. The animation is fully configured and ready — just not running. Worth noting: calling .seek(0) before .play() resets it to the first frame if you want re-triggerable on-enter animations.

For parallax and scroll-progress-driven effects, wire the animation's .seek() method to a scroll event. Seeking is cheap in v4 — the engine is optimized for it, unlike v3 where rapid seeking could cause visual artifacts at sub-16ms intervals.

If you're building a UI-heavy marketing page with lots of on-enter effects, consider pairing anime.js with Empire UI's pre-built components from /glassmorphism or the gradient generator — you get the static design layer handled, and you layer anime.js entrances on top without fighting two animation systems.

Performance, Gotchas, and When to Use Something Else

Anime.js v4 is fast. The internal scheduler was rewritten to use a single requestAnimationFrame loop shared across all active animations, which is a meaningful improvement over v3 where each animation could have its own rAF. On a 2022-era mid-range Android device animating 40 staggered elements, v4 sits comfortably at 60fps where v3 occasionally dropped frames.

That said, you can still wreck performance with it. Animating width, height, top, and left triggers layout and paint on every frame. Always animate transform and opacity instead — these run on the compositor thread and won't cause reflows. This isn't anime.js-specific advice, but it's where most people run into jank in practice.

When should you use Framer Motion instead? If you're in a React codebase and your animations are primarily tied to component mount/unmount lifecycle, Framer Motion's AnimatePresence is genuinely better-suited than wiring IntersectionObservers manually. Anime.js wins when you need precise multi-element choreography, SVG drawing, path following, or when you're working outside React entirely.

One more thing — bundle size. Anime.js v4 is around 17kb minified and gzipped. That's heavier than a single CSS animation but lighter than Framer Motion. If you're doing one or two simple transitions on a landing page, you don't need either — Tailwind CSS animations or plain CSS keyframes will get you there at zero JS cost. Reach for anime.js when the choreography genuinely needs a timeline.

FAQ

Is Anime.js v4 backward-compatible with v3 code?

No. The import surface changed entirely — there's no default export, and anime.timeline() was replaced by createTimeline(). Expect to rewrite timeline and morph code if upgrading from v3.

Can I use Anime.js v4 with React and Next.js?

Yes, but you'll need to initialize animations inside useEffect or after DOM mount. There's no React wrapper — you're working directly with DOM nodes, which is fine in App Router with 'use client'.

Does Anime.js v4 support SVG morphing between paths?

Path morphing is supported via svg.morphTo(), but both paths must have the same number of points. If they don't, you'll need to normalize them with an external tool like Flubber or GSAP's MorphSVG before animating.

What's the difference between stagger delay and stagger duration in v4?

Delay stagger offsets when each element starts animating. Duration stagger changes how long each element takes to complete. You can combine both — elements can start at different times AND run for different lengths.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Web Animations API in 2026: Native Animations Without a LibraryCSS Shape Morphing: clip-path, offset-path and SVG Morph TechniquesAdvanced SVG Animation: stroke-dasharray, SMIL and JavaScript SyncHTML Canvas Animations in React: Particles, Noise Fields, More