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
- MDN: Web Animations API
- MDN: Element.animate()
- MDN: Animation Interface
- W3C: Web Animations Specification
Tools and Libraries
- Web Animations Polyfill
- Anime.js - A lightweight JavaScript animation library that works well with the Web Animations API
- Cubic Bezier Generator - Tool for creating custom easing functions
Tutorials and Articles
- CSS-Tricks: CSS Animations vs Web Animations API
- Google Developers: Animations and Performance
- web.dev: Animation Guide
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.