animation-timing-function & animation-delay
animation-timing-function controls the speed curve of an animation (ease, ease-in, ease-out, linear, cubic-bezier, steps). animation-delay controls when the animation starts. Together they control the feel and timing of animations. Use delays for staggered effects; choose timing functions that match your animation's purpose (bouncy, smooth, mechanical, etc.).
Timing function types
Function | Feel | Use Case |
|---|---|---|
linear | Constant speed | Continuous, mechanical motion |
ease (default) | Slow start/end, fast middle | General purpose animations |
ease-in | Slow start, then fast | Elements entering/appearing |
ease-out | Fast start, then slow | Elements exiting/disappearing |
ease-in-out | Slow start/end | Smooth, elegant transitions |
cubic-bezier() | Custom | Precise control |
steps() | Discrete steps | Sprite animations, frame-by-frame |
/* Linear: constant speed */
@keyframes linear-motion {
from { transform: translateX(0); }
to { transform: translateX(200px); }
}
.linear-element {
animation: linear-motion 2s linear;
/* Moves at same speed throughout */
}
/* Ease (default): slow start, fast middle, slow end */
@keyframes ease-motion {
from { transform: translateX(0); }
to { transform: translateX(200px); }
}
.ease-element {
animation: ease-motion 2s ease;
/* Natural acceleration/deceleration */
}
/* Ease-in: slow start, fast end */
@keyframes ease-in-motion {
from { opacity: 0; }
to { opacity: 1; }
}
.ease-in-element {
animation: ease-in-motion 1s ease-in;
/* Gradually picks up speed */
}
/* Ease-out: fast start, slow end */
@keyframes ease-out-motion {
from { opacity: 1; }
to { opacity: 0; }
}
.ease-out-element {
animation: ease-out-motion 1s ease-out;
/* Gradually slows down */
}
/* Ease-in-out: slow start and end, fast middle */
@keyframes ease-in-out-motion {
from { transform: translateY(0); }
to { transform: translateY(100px); }
}
.ease-in-out-element {
animation: ease-in-out-motion 1.5s ease-in-out;
/* Smooth, elegant acceleration */
}
/* Cubic-bezier: custom timing */
@keyframes custom-motion {
from { transform: scale(1); }
to { transform: scale(1.5); }
}
.custom-element {
animation: custom-motion 2s cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Custom bouncy effect */
}
/* Steps: frame-by-frame animation */
@keyframes step-animation {
0% { background-position: 0 0; }
100% { background-position: 100% 0; }
}
.sprite-animation {
animation: step-animation 1s steps(12, end);
/* 12 discrete frames */
}Cubic-bezier deep dive
/* cubic-bezier(x1, y1, x2, y2) */
/* Defines control points of a bezier curve */
/* Predefined timing functions as cubic-bezier equivalents */
.ease { animation: move 1s cubic-bezier(0.25, 0.1, 0.25, 1); }
.ease-in { animation: move 1s cubic-bezier(0.42, 0, 1, 1); }
.ease-out { animation: move 1s cubic-bezier(0, 0, 0.58, 1); }
.ease-in-out { animation: move 1s cubic-bezier(0.42, 0, 0.58, 1); }
/* Common custom curves */
/* Bouncy effect: y values exceed 0-1 range */
.bouncy {
animation: bounce 1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Overshoots then returns */
}
/* Snappy: fast middle */
.snappy {
animation: change 0.5s cubic-bezier(0.17, 0.67, 0.83, 0.67);
}
/* Smooth: gentle curves */
.smooth {
animation: move 2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* Sharp: quick acceleration */
.sharp {
animation: enter 0.8s cubic-bezier(0.3, 0, 0.8, 0.15);
}
/* Tools for creating cubic-bezier */
/* Use cubic-bezier.com or DevTools to visualize */
/* Browser DevTools can animate cubic-bezier curves */
.test-timing {
animation: test 3s cubic-bezier(0.1, 0.7, 0.9, 0.2);
/* Adjust in DevTools to see effect in real-time */
}animation-delay for staggered effects
/* Single animation with delay */
.delayed-element {
opacity: 0;
animation: fade-in 0.6s ease forwards;
animation-delay: 0.3s;
/* Waits 0.3s, then fades in */
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Staggered list animations */
.list-item {
opacity: 0;
transform: translateY(20px);
animation: slide-up 0.5s ease forwards;
}
.list-item:nth-child(1) { animation-delay: 0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
.list-item:nth-child(4) { animation-delay: 0.3s; }
.list-item:nth-child(5) { animation-delay: 0.4s; }
@keyframes slide-up {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* Dynamic delay with CSS custom properties */
.dynamic-item {
animation: slide-up 0.5s ease forwards;
animation-delay: var(--index) * 0.1s;
/* Calculated based on item position */
}
/* Multiple animations with different delays */
.complex {
animation:
fade 0.5s ease forwards,
slide 0.5s ease 0.2s forwards,
scale 0.5s ease 0.4s forwards;
/* Each animation starts at different time */
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide { from { transform: translateX(-50px); } to { transform: translateX(0); } }
@keyframes scale { from { transform: scale(0.8); } to { transform: scale(1); } }Steps timing function
/* steps(number-of-steps, direction) */
/* Divides animation into discrete steps instead of smooth curve */
/* 4-step animation */
.four-step {
animation: move 2s steps(4, end);
/* Animation moves in 4 discrete jumps */
}
/* 12-step sprite animation */
.sprite {
width: 50px;
height: 50px;
background: url('sprite.png') 0 0;
animation: animate-sprite 2s steps(12, end) infinite;
}
@keyframes animate-sprite {
from { background-position: 0 0; }
to { background-position: 600px 0; }
}
/* steps(n, start) vs steps(n, end) */
.steps-end {
animation: blink 1s steps(2, end);
/* Changes at end of each step */
}
.steps-start {
animation: blink 1s steps(2, start);
/* Changes at start of each step */
}
@keyframes blink {
from { opacity: 0; }
to { opacity: 1; }
}
/* Jump-start effect */
.jump-start {
animation: move 1s steps(5, start);
/* First frame shows immediately */
}
/* Jump-end effect (default) */
.jump-end {
animation: move 1s steps(5, end);
/* First frame waits for first step */
}
/* Film strip animation */
@keyframes film-strip {
from { background-position: 0 0; }
to { background-position: -2400px 0; }
}
.film {
width: 200px;
height: 100px;
background: url('film.png') 0 0;
animation: film-strip 3s steps(12, end) infinite;
/* Shows 12 frames in 3 seconds */
}Practical timing examples
/* Loading spinner: linear and infinite */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
/* Constant rotation speed */
}
/* Bouncy entrance */
@keyframes bounce-in {
0% { transform: scale(0); opacity: 0; }
70% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(1); }
}
.bounce-element {
animation: bounce-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Bouncy overshoot effect */
}
/* Smooth fade-in */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.smooth-fade {
animation: fade-in 1s ease-in-out;
animation-delay: 0.2s;
}
/* Typing effect with steps */
@keyframes typing {
from { width: 0; }
to { width: 100%; }
}
.typing-text {
max-width: 300px;
overflow: hidden;
white-space: nowrap;
border-right: 3px solid #333;
animation: typing 3s steps(30, end);
/* Types 30 characters over 3 seconds */
}
/* Pulsing animation with ease-in-out */
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.6; transform: scale(1.05); }
}
.pulse-element {
animation: pulse 2s ease-in-out infinite;
/* Smooth breathing effect */
}
/* Staggered entrance */
.card {
opacity: 0;
transform: translateY(30px);
animation: slide-up 0.6s ease-out forwards;
}
.card:nth-child(1) { animation-delay: 0s; }
.card:nth-child(2) { animation-delay: 0.2s; }
.card:nth-child(3) { animation-delay: 0.4s; }
@keyframes slide-up {
to { opacity: 1; transform: translateY(0); }
}Performance and optimization
/* GPU-accelerated properties animate smoother */
.good-animation {
animation: move 2s ease-in-out;
}
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
/* Avoid animating layout properties */
.bad-animation {
animation: bad-move 2s ease-in-out;
}
@keyframes bad-move {
from { left: 0; }
to { left: 100px; }
}
/* Use will-change for complex animations */
.optimized {
will-change: transform;
animation: rotate 10s linear infinite;
}
/* Keep timing functions simple */
.simple-timing {
animation-timing-function: ease-in-out;
}
.complex-timing {
animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
/* More complex curves use more CPU */
}
/* Prefers reduced motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-delay: 0 !important;
}
}