Web Animations API

Introduction to Web Animations API

The Web Animations API provides a powerful, high-performance way to create and control animations in JavaScript. It combines the flexibility of JavaScript animations with the performance benefits of CSS animations.

Key Benefits

  • Performance: Runs on the browser's animation engine, similar to CSS animations
  • Control: Fine-grained control over animation playback (pause, reverse, speed, etc.)
  • Flexibility: Create complex animations programmatically
  • Timing: Precise timing control with various timing functions
Browser Support: The Web Animations API is supported in all modern browsers. For older browsers, consider using a polyfill like web-animations-js.

Basic Animation with element.animate()

The simplest way to use the Web Animations API is with the element.animate() method:


// Basic syntax
element.animate(keyframes, options);

// Example: Fade and move an element
const element = document.querySelector('.my-element');

const keyframes = [
  { opacity: 0, transform: 'translateY(20px)' },  // Starting state
  { opacity: 1, transform: 'translateY(0)' }      // Ending state
];

const options = {
  duration: 1000,       // Animation duration in milliseconds
  easing: 'ease-out',   // Timing function
  fill: 'forwards'      // Keep the final state after animation completes
};

// Create and start the animation
const animation = element.animate(keyframes, options);

// The animate() method returns an Animation object
console.log(animation); // Animation object with various properties and methods
                        

Keyframes Formats

Keyframes can be specified in different formats:


// Array of keyframe objects (from-to)
const keyframes1 = [
  { opacity: 0 },  // Starting state
  { opacity: 1 }   // Ending state
];

// Array of keyframe objects with multiple properties
const keyframes2 = [
  { opacity: 0, transform: 'scale(0.8)' },  // Starting state
  { opacity: 1, transform: 'scale(1)' }     // Ending state
];

// Array of keyframe objects with offsets (0 to 1)
const keyframes3 = [
  { opacity: 0, offset: 0 },      // 0% of the animation
  { opacity: 0.5, offset: 0.3 },  // 30% of the animation
  { opacity: 0.8, offset: 0.8 },  // 80% of the animation
  { opacity: 1, offset: 1 }       // 100% of the animation
];

// Object format with property arrays
const keyframes4 = {
  opacity: [0, 0.5, 1],                              // Three keyframes
  transform: ['scale(0.8)', 'scale(0.9)', 'scale(1)'] // Three keyframes
};
                        

Animation Options

The options parameter allows you to control various aspects of the animation:


const options = {
  // Timing options
  duration: 1000,          // Duration in milliseconds
  delay: 200,              // Delay before starting in milliseconds
  endDelay: 0,             // Delay after animation in milliseconds
  easing: 'ease-in-out',   // Timing function
  
  // Iteration options
  iterations: 3,           // Number of times to repeat (Infinity for endless)
  direction: 'alternate',  // 'normal', 'reverse', 'alternate', 'alternate-reverse'
  
  // Fill mode
  fill: 'forwards',        // 'none', 'forwards', 'backwards', 'both'
  
  // Playback rate (speed)
  playbackRate: 1.5        // 1 is normal, <1 is slower, >1 is faster
};
                        

Controlling Animations

The animate() method returns an Animation object that provides methods to control playback:


const element = document.querySelector('.my-element');
const animation = element.animate(
  [
    { transform: 'translateX(0)' },
    { transform: 'translateX(300px)' }
  ], 
  { 
    duration: 3000,
    fill: 'forwards'
  }
);

// Pause the animation
document.getElementById('pause-btn').addEventListener('click', () => {
  animation.pause();
});

// Play/resume the animation
document.getElementById('play-btn').addEventListener('click', () => {
  animation.play();
});

// Cancel the animation (stops and removes effects)
document.getElementById('cancel-btn').addEventListener('click', () => {
  animation.cancel();
});

// Reverse the animation direction
document.getElementById('reverse-btn').addEventListener('click', () => {
  animation.reverse();
});

// Jump to a specific point (0 to 1)
document.getElementById('seek-btn').addEventListener('click', () => {
  animation.currentTime = animation.effect.getTiming().duration / 2; // Jump to midpoint
});

// Change playback rate (speed)
document.getElementById('speed-btn').addEventListener('click', () => {
  animation.playbackRate = 2; // Double speed
});
                        

Animation States and Events

You can monitor animation state and respond to events:


// Check animation state
console.log(animation.playState); // 'running', 'paused', 'finished', 'idle'

// Animation events
animation.onfinish = () => {
  console.log('Animation finished!');
  // Do something when animation completes
};

animation.oncancel = () => {
  console.log('Animation was cancelled');
};

// Using event listeners
animation.addEventListener('finish', () => {
  console.log('Animation finished! (event listener)');
});
                        
Pro Tip: You can store animations in variables to control them later, making it easy to create complex interactive animations that respond to user actions.

Advanced Animation Techniques

Chaining Animations

You can chain animations to create sequences:


const element = document.querySelector('.my-element');

// First animation: fade in
const fadeIn = element.animate(
  [
    { opacity: 0 },
    { opacity: 1 }
  ],
  { duration: 1000, fill: 'forwards' }
);

// Second animation: move right (starts after first animation)
fadeIn.onfinish = () => {
  const moveRight = element.animate(
    [
      { transform: 'translateX(0)' },
      { transform: 'translateX(200px)' }
    ],
    { duration: 1000, fill: 'forwards' }
  );
  
  // Third animation: scale up (starts after second animation)
  moveRight.onfinish = () => {
    element.animate(
      [
        { transform: 'translateX(200px) scale(1)' },
        { transform: 'translateX(200px) scale(1.5)' }
      ],
      { duration: 1000, fill: 'forwards' }
    );
  };
};
                        

Grouping Animations with Animation Groups

For more complex scenarios, you can use the more advanced parts of the API:


// Create a sequence of animations
const sequence = new SequenceEffect([
  new KeyframeEffect(element1, keyframes1, timing1),
  new KeyframeEffect(element2, keyframes2, timing2),
  new KeyframeEffect(element3, keyframes3, timing3)
]);

// Create a group of animations that run in parallel
const group = new GroupEffect([
  new KeyframeEffect(element1, keyframes1, timing1),
  new KeyframeEffect(element2, keyframes2, timing2),
  new KeyframeEffect(element3, keyframes3, timing3)
]);

// Play the sequence or group
const animation = new Animation(sequence);
animation.play();
                        
Note: GroupEffect and SequenceEffect are part of the level 2 spec and may not be supported in all browsers yet. Check browser compatibility before using these features.

Custom Timing Functions

Create custom easing with cubic-bezier:


// Custom easing function
const customEasing = 'cubic-bezier(0.68, -0.55, 0.27, 1.55)'; // Bouncy effect

const animation = element.animate(
  [
    { transform: 'scale(1)' },
    { transform: 'scale(1.5)' }
  ],
  { 
    duration: 1000, 
    easing: customEasing,
    fill: 'forwards'
  }
);
                        

Practical Examples

Animated Notification Badge


function animateNotification(element) {
  // Scale up and down with a slight bounce
  return element.animate(
    [
      { transform: 'scale(1)', backgroundColor: '#ff5252' },
      { transform: 'scale(1.3)', backgroundColor: '#ff0000', offset: 0.3 },
      { transform: 'scale(1.1)', backgroundColor: '#ff0000', offset: 0.5 },
      { transform: 'scale(1.2)', backgroundColor: '#ff0000', offset: 0.7 },
      { transform: 'scale(1)', backgroundColor: '#ff5252' }
    ],
    { 
      duration: 1000,
      easing: 'ease-in-out',
      iterations: 1
    }
  );
}

// Usage
const badge = document.querySelector('.notification-badge');
document.getElementById('new-message-btn').addEventListener('click', () => {
  animateNotification(badge);
});
                        

Interactive Slider


const slider = document.querySelector('.slider');
const slides = document.querySelectorAll('.slide');
let currentIndex = 0;

function moveToSlide(index) {
  // Calculate the target position
  const targetPosition = -index * 100 + '%';
  
  // Animate the slider
  return slider.animate(
    [
      { transform: `translateX(${getComputedStyle(slider).transform})` },
      { transform: `translateX(${targetPosition})` }
    ],
    { 
      duration: 500,
      easing: 'ease-out',
      fill: 'forwards'
    }
  );
}

// Next slide button
document.getElementById('next-btn').addEventListener('click', () => {
  if (currentIndex < slides.length - 1) {
    currentIndex++;
    moveToSlide(currentIndex);
  }
});

// Previous slide button
document.getElementById('prev-btn').addEventListener('click', () => {
  if (currentIndex > 0) {
    currentIndex--;
    moveToSlide(currentIndex);
  }
});
                        

Loading Spinner


function createSpinner(element) {
  // Continuous rotation animation
  return element.animate(
    [
      { transform: 'rotate(0deg)' },
      { transform: 'rotate(360deg)' }
    ],
    { 
      duration: 1000,
      iterations: Infinity,
      easing: 'linear'
    }
  );
}

// Usage
const spinner = document.querySelector('.spinner');
const spinnerAnimation = createSpinner(spinner);

// Show/hide spinner based on loading state
function setLoading(isLoading) {
  if (isLoading) {
    spinner.style.display = 'block';
    spinnerAnimation.play();
  } else {
    spinnerAnimation.finish();
    spinner.style.display = 'none';
  }
}

// Example usage
document.getElementById('load-data-btn').addEventListener('click', () => {
  setLoading(true);
  
  // Simulate API call
  setTimeout(() => {
    setLoading(false);
  }, 3000);
});
                        

Performance Optimization

Best Practices

  • Use transform and opacity: These properties are hardware-accelerated in most browsers
  • Avoid animating layout properties: Properties like width, height, margin, or padding can trigger expensive layout recalculations
  • Reduce DOM updates: Batch your DOM changes before starting animations
  • Use requestAnimationFrame for custom animations: When the Web Animations API doesn't meet your needs
  • Test on target devices: Performance can vary significantly across devices

Monitoring Performance


// Check if animation is running on the compositor thread
const animation = element.animate(
  [
    { transform: 'translateX(0)' },
    { transform: 'translateX(300px)' }
  ], 
  { duration: 1000 }
);

// In Chrome DevTools, you can see if the animation is compositor-accelerated
console.log('Check DevTools Performance panel to see if this animation is optimized');
                        
Pro Tip: Use Chrome DevTools Performance panel to identify jank and optimize your animations. Look for the "Rendering" tab to enable paint flashing and see which areas are being repainted.

Comparison with Other Animation Methods

Feature Web Animations API CSS Animations JavaScript Animations (manual)
Performance High (browser optimized) High (browser optimized) Varies (depends on implementation)
Control High (play, pause, reverse, etc.) Limited (CSS variables or classes) High (full programmatic control)
Complexity Medium Low for simple, High for complex High (requires more code)
Dynamic Values Easy to implement Requires CSS variables or JS updates Easy to implement
Browser Support Good (may need polyfill) Excellent Excellent
When to Use Web Animations API:
  • When you need programmatic control over animations
  • For complex animation sequences
  • When animations need to respond to user input
  • When you want the performance of CSS with the flexibility of JavaScript

Resources and Further Reading

Documentation

Tools and Libraries

Tutorials and Articles

Final Thought: The Web Animations API bridges the gap between CSS animations and JavaScript animations, offering the best of both worlds. As browser support continues to improve, it's becoming an essential tool for creating high-performance, interactive web animations.