Skip to main content
Learn how to create high-performance animations with Anime.js.

Engine Architecture

Anime.js uses a single, shared animation engine that manages all active animations.

Request Animation Frame

The engine uses requestAnimationFrame for optimal timing:
import { engine } from 'animejs';

// Engine automatically starts when needed
console.log('Engine running:', !engine.paused);

// Manually control engine
engine.pause();
engine.resume();

Frame Rate Control

import { animate } from 'animejs';

// Limit to 30fps for non-critical animations
animate('.background', {
  translateY: 100,
  duration: 2000,
  frameRate: 30
});
Lower frame rates reduce CPU usage but may appear less smooth. Use for background or ambient animations.

Transform Performance

Transforms are GPU-accelerated and perform best.

Prefer Transforms

// ❌ Avoid (triggers layout)
animate('.box', {
  left: 200,
  top: 100,
  duration: 1000
});

// ✅ Better (GPU accelerated)
animate('.box', {
  translateX: 200,
  translateY: 100,
  duration: 1000
});

Transform Priority

1

Use translate instead of left/top

translateX: 100  // GPU
left: 100        // CPU + layout
2

Use scale instead of width/height

scale: 1.5       // GPU
width: 200       // CPU + layout
3

Use opacity for fading

opacity: 0.5     // GPU (composited)

GPU Acceleration

Force GPU Acceleration

Use will-change or 3D transforms:
.animated-element {
  will-change: transform, opacity;
}
// Or use translate3d
animate('.box', {
  translateZ: 0, // Forces GPU layer
  translateX: 100,
  duration: 1000
});
Don’t overuse will-change. Too many GPU layers consume memory. Remove it after animation completes.

Optimal Properties for GPU

  • transform (all transform functions)
  • opacity
  • filter (some filters)

Batching & Reflow

Batch DOM Reads and Writes

// ❌ Causes layout thrashing
elements.forEach(el => {
  const width = el.offsetWidth; // Read
  el.style.width = width * 2 + 'px'; // Write
});

// ✅ Batch reads, then writes
const widths = elements.map(el => el.offsetWidth);
widths.forEach((width, i) => {
  elements[i].style.width = width * 2 + 'px';
});

Anime.js Auto-Batching

The engine automatically batches property updates:
// These are batched in a single frame
animate('.box1', { translateX: 100, duration: 1000 });
animate('.box2', { translateY: 100, duration: 1000 });
animate('.box3', { scale: 1.5, duration: 1000 });

Memory Management

Clean Up Completed Animations

const animation = animate('.box', {
  translateX: 200,
  duration: 1000,
  onComplete: (anim) => {
    anim.cancel(); // Release memory
  }
});

Reuse Timelines

import { timeline } from 'animejs';

const tl = timeline({
  loop: true,
  autoplay: false
});

tl.add('.box', { translateX: 100, duration: 500 });
tl.add('.box', { translateY: 100, duration: 500 });

// Reuse timeline
button.addEventListener('click', () => {
  tl.restart();
});

Avoid Memory Leaks

// ❌ Creates new timeline on each call
function animate Box() {
  return timeline()
    .add('.box', { translateX: 100 });
}

// ✅ Reuse timeline
const boxTimeline = timeline({ autoplay: false });
boxTimeline.add('.box', { translateX: 100 });

function animateBox() {
  boxTimeline.restart();
}

WAAPI for Performance

Use WAAPI for simple, high-performance animations:
import { waapi } from 'animejs';

// Runs on compositor thread
waapi.animate('.box', {
  translateX: 200,
  opacity: 0.5,
  duration: 1000
});
WAAPI animations can run independently of the main thread, providing better performance during heavy JavaScript execution.

Optimizing Large Sets

Limit Concurrent Animations

import { animate, stagger } from 'animejs';

// ❌ 1000 elements at once
animate('.item', {
  translateY: 100,
  duration: 1000
});

// ✅ Stagger for better performance
animate('.item', {
  translateY: 100,
  duration: 1000,
  delay: stagger(50, { limit: 100 }) // Only 100 active at once
});

Virtual Scrolling

For very large lists:
// Only animate visible elements
const visibleItems = items.filter(item => {
  const rect = item.getBoundingClientRect();
  return rect.top < window.innerHeight && rect.bottom > 0;
});

animate(visibleItems, {
  opacity: [0, 1],
  duration: 600
});

Easing Performance

Spring vs Bezier

// Springs are computationally expensive
animate('.box', {
  translateX: 100,
  ease: spring({ mass: 1, stiffness: 100, damping: 10 }),
  duration: 1000
});

// Bezier curves are faster
animate('.box', {
  translateX: 100,
  ease: 'out(3)', // Bezier approximation
  duration: 1000
});
Use springs for special effects, bezier curves for general animations.

Composition Optimization

Replace vs Blend

// ✅ Replace is more efficient
animate('.box', {
  translateX: 100,
  composition: 'replace', // Cancels previous
  duration: 1000
});

// ⚠️ Blend adds overhead
animate('.box', {
  translateX: 100,
  composition: 'blend', // Adds layer
  duration: 1000
});

Monitoring Performance

Frame Timing

import { engine } from 'animejs';

let frameCount = 0;
let lastTime = performance.now();

setInterval(() => {
  const now = performance.now();
  const fps = frameCount / ((now - lastTime) / 1000);
  console.log('FPS:', fps.toFixed(2));
  frameCount = 0;
  lastTime = now;
}, 1000);

engine.addEventListener('update', () => {
  frameCount++;
});

Chrome DevTools

  1. Open Performance tab
  2. Start recording
  3. Trigger animation
  4. Stop and analyze:
    • Frame rate graph
    • Paint events
    • Layout shifts
    • JavaScript execution

Best Practices

Stick to GPU-accelerated properties:
animate('.box', {
  translateX: 100,
  translateY: 50,
  rotate: 45,
  scale: 1.5,
  opacity: 0.8,
  duration: 1000
});
Properties that trigger layout are slow:
// Avoid
width, height, top, left, padding, margin, border

// Prefer
transform, opacity
Delay expensive recalculations:
import { debounce } from 'lodash';

const handleResize = debounce(() => {
  // Expensive calculation
  animate('.box', { /* ... */ });
}, 150);

window.addEventListener('resize', handleResize);
Always test on lower-end devices:
// Throttle CPU in Chrome DevTools
// Performance > CPU: 4x/6x slowdown

Performance Checklist

1

Use transforms over position

translateX/Y instead of left/top
2

Add will-change strategically

✅ Only on elements being animated❌ Don’t apply globally
3

Limit concurrent animations

✅ Use stagger with limits✅ Consider frame rate reduction
4

Clean up when done

✅ Cancel completed animations✅ Remove event listeners
5

Test on real devices

✅ Test on mobile/tablet✅ Use Chrome DevTools throttling

Advanced: Precision Control

import { engine } from 'animejs';

// Reduce precision for better performance
engine.precision = 2; // Default is 4

// Values rounded to 2 decimal places
animate('.box', {
  translateX: 100.12345, // Becomes 100.12
  duration: 1000
});
Lower precision may cause jittery animations on very slow movements. Only reduce if needed.

Time Unit

Use seconds for easier debugging:
import { engine } from 'animejs';

// Switch to seconds
engine.timeUnit = 's';

animate('.box', {
  translateX: 100,
  duration: 1,  // 1 second
  delay: 0.5    // 500ms
});

Benchmarks

Typical performance on modern hardware:
Animation TypeElementsFPS
Transform only10060
Transform only50060
Mixed properties10055-60
Mixed properties50045-55
Complex paths5060
SVG morphing2055-60
Aim for consistent 60fps. Drop below 30fps becomes noticeably choppy.

Build docs developers (and LLMs) love