Keyframes allow you to define animations with multiple intermediate steps, giving you fine-grained control over complex animation sequences.
Anime.js supports two keyframe formats: duration-based and percentage-based.
Duration-Based Keyframes
Define keyframes as an array of objects, each with its own properties:
import { animate } from 'animejs';
animate('.box', {
keyframes: [
{ x: 0, y: 0 },
{ x: 100, y: 50, duration: 500 },
{ x: 100, y: 100, duration: 800 },
{ x: 0, y: 100, duration: 500 }
]
});
Each keyframe can have its own timing parameters:
animate('.box', {
keyframes: [
{
scale: 1.5,
duration: 500,
ease: 'easeInQuad'
},
{
scale: 0.5,
duration: 300,
ease: 'easeOutQuad'
},
{
scale: 1,
duration: 400,
ease: 'linear'
}
]
});
Percentage-Based Keyframes
Define keyframes at specific percentage points of the animation:
animate('.box', {
duration: 2000,
keyframes: {
'0%': { x: 0, y: 0 },
'25%': { x: 100, y: 0 },
'50%': { x: 100, y: 100 },
'75%': { x: 0, y: 100 },
'100%': { x: 0, y: 0 }
}
});
Add easing to percentage keyframes:
animate('.box', {
duration: 3000,
keyframes: {
'0%': { scale: 1 },
'30%': { scale: 1.5, ease: 'easeInQuad' },
'70%': { scale: 0.8, ease: 'easeOutQuad' },
'100%': { scale: 1 }
}
});
Property Keyframes
Apply keyframes to individual properties:
animate('.box', {
x: [
{ value: 100, duration: 500, ease: 'easeInQuad' },
{ value: 50, duration: 300, ease: 'easeOutQuad' },
{ value: 150, duration: 400, ease: 'linear' }
],
opacity: [
{ value: 0.5, duration: 600 },
{ value: 1, duration: 600 }
]
});
Simplified Property Keyframes
For values only (equal duration split):
animate('.box', {
x: [0, 100, 50, 150], // Each step gets 1/4 of total duration
opacity: [1, 0.5, 0.8, 0],
duration: 2000
});
From-To Keyframes
Specify start values for keyframe sequences:
animate('.box', {
keyframes: [
{
from: 0,
to: 100,
duration: 500
},
{
to: 50,
duration: 300
},
{
to: 150,
duration: 400
}
]
});
Timeline Keyframes Example
Complex choreography with inline keyframes:
import { createTimeline, stagger } from 'animejs';
const tl = createTimeline({
defaults: {
ease: 'inOutQuad',
duration: 600
}
});
tl.add('.cursor', {
keyframes: [
{ scale: 0.625 },
{ scale: 1.125 },
{ scale: 1 }
],
duration: 600
})
.add('.dot', {
keyframes: [
{
x: stagger('-.175rem', { grid: [10, 10], from: 0, axis: 'x' }),
y: stagger('-.175rem', { grid: [10, 10], from: 0, axis: 'y' }),
duration: 200
},
{
x: stagger('.125rem', { grid: [10, 10], from: 0, axis: 'x' }),
y: stagger('.125rem', { grid: [10, 10], from: 0, axis: 'y' }),
scale: 2,
duration: 500
},
{
x: 0,
y: 0,
scale: 1,
duration: 600
}
],
delay: stagger(50, { grid: [10, 10], from: 0 })
}, 0);
Seamless Loop Keyframes
Create perfect loops with keyframes:
import { createTimeline } from 'animejs';
const loopDuration = 6000;
const animDuration = loopDuration * 0.2;
const tl = createTimeline({
defaults: {
ease: 'inOutSine',
loopDelay: (loopDuration * 0.2) - animDuration,
duration: animDuration
}
});
// Add seamlessly looping keyframes
tl.add('.element', {
keyframes: [
{
backgroundColor: 'hsl(180, 60%, 80%)',
scale: 1.25
},
{
backgroundColor: 'hsl(0, 40%, 60%)',
scale: 1
}
],
loop: -1 // Infinite loop
});
Mixed Property Keyframes
Combine regular properties with keyframed properties:
animate('.box', {
// Regular property
rotate: 360,
// Keyframed properties
x: [0, 100, 50],
y: [
{ value: 0, duration: 500 },
{ value: 100, duration: 800 },
{ value: 0, duration: 500 }
],
// Overall duration
duration: 2000
});
Keyframe Generation
From the source code, here’s how keyframes are internally processed:
// Simplified from src/animation/animation.js
const generateKeyframes = (keyframes, parameters) => {
const properties = {};
if (isArr(keyframes)) {
// Duration-based keyframes
const propertyNames = keyframes
.flatMap(key => Object.keys(key))
.filter(isKey);
for (let propName of propertyNames) {
const propArray = keyframes.map(key => {
const newKey = {};
for (let p in key) {
if (p === propName) {
newKey.to = key[p];
} else if (isKey(p)) {
// Skip other properties
} else {
// Copy timing parameters
newKey[p] = key[p];
}
}
return newKey;
});
properties[propName] = propArray;
}
} else {
// Percentage-based keyframes
const totalDuration = parameters.duration;
const keys = Object.keys(keyframes)
.map(key => ({
offset: parseFloat(key) / 100,
props: keyframes[key]
}))
.sort((a, b) => a.offset - b.offset);
// Convert to duration-based format
// ... conversion logic
}
return properties;
}
Advanced Keyframe Patterns
Bounce Effect
animate('.ball', {
keyframes: [
{ y: 0, ease: 'easeInQuad' },
{ y: 200, ease: 'easeOutQuad' },
{ y: 0, ease: 'easeInQuad' },
{ y: 150, ease: 'easeOutQuad' },
{ y: 0, ease: 'easeInQuad' },
{ y: 100, ease: 'easeOutQuad' },
{ y: 0 }
],
duration: 2000
});
Wave Motion
animate('.wave', {
keyframes: [
{ y: 0, rotate: 0 },
{ y: -20, rotate: 5 },
{ y: 0, rotate: 0 },
{ y: 20, rotate: -5 },
{ y: 0, rotate: 0 }
],
duration: 1000,
loop: true
});
Path Following
animate('.follower', {
keyframes: [
{ x: 0, y: 0 },
{ x: 100, y: 20 },
{ x: 200, y: 0 },
{ x: 300, y: -20 },
{ x: 400, y: 0 }
],
duration: 3000,
ease: 'linear'
});
Color Keyframes
Animate through multiple colors:
animate('.box', {
backgroundColor: [
{ value: '#FF4B4B', duration: 500 },
{ value: '#FFC730', duration: 500 },
{ value: '#4BFF4B', duration: 500 },
{ value: '#4B4BFF', duration: 500 }
]
});
// Or with percentage syntax
animate('.box', {
duration: 2000,
keyframes: {
'0%': { backgroundColor: '#FF4B4B' },
'25%': { backgroundColor: '#FFC730' },
'50%': { backgroundColor: '#4BFF4B' },
'75%': { backgroundColor: '#4B4BFF' },
'100%': { backgroundColor: '#FF4B4B' }
}
});
Keyframe Timing
Control individual keyframe durations and delays:
animate('.box', {
keyframes: [
{
x: 100,
duration: 1000,
delay: 0,
ease: 'easeInQuad'
},
{
x: 200,
duration: 500,
// No delay - starts after previous
ease: 'linear'
},
{
x: 0,
duration: 800,
ease: 'easeOutQuad'
}
]
});
Function-Based Keyframes
Use functions within keyframes:
animate('.box', {
x: [
{ value: (el, i) => i * 50, duration: 500 },
{ value: (el, i) => i * 100, duration: 500 },
{ value: 0, duration: 500 }
]
});
Best Practices
Use percentage keyframes for complex timing
Percentage-based keyframes make it easier to visualize animation timing.animate('.element', {
duration: 3000,
keyframes: {
'0%': { scale: 1, opacity: 1 },
'30%': { scale: 1.5, opacity: 0.8 },
'70%': { scale: 0.8, opacity: 0.6 },
'100%': { scale: 1, opacity: 1 }
}
});
Don’t mix too many properties in keyframe objects.// Good - focused on position
animate('.box', {
keyframes: [
{ x: 0, y: 0 },
{ x: 100, y: 50 },
{ x: 0, y: 100 }
]
});
// Consider separate animations for clarity
animate('.box', {
x: [0, 100, 0],
y: [0, 50, 100]
});
Specify easing per keyframe for polish
Different easing on each step creates more natural motion.animate('.ball', {
keyframes: [
{ y: 0, ease: 'easeInQuad' }, // Accelerate down
{ y: 200, ease: 'easeOutQuad' }, // Decelerate up
{ y: 0 }
]
});
API Reference
Duration-Based Keyframes
keyframes: Array<{
[property: string]: any;
duration?: number;
delay?: number;
ease?: string | function;
}>
Percentage-Based Keyframes
keyframes: {
[percentage: string]: {
[property: string]: any;
ease?: string | function;
}
}
Property Keyframes
propertyName: Array<value | {
value: any;
duration?: number;
delay?: number;
ease?: string | function;
}>
Keyframes are automatically normalized internally, so you can mix and match formats based on what’s most convenient for your use case.