Create complex, multi-step animations with @keyframes and animation properties
CSS Animations allow you to create complex, multi-step animations without JavaScript. Unlike transitions which require a trigger (like hover), animations can run automatically, loop infinitely, and have multiple intermediate steps defined with @keyframes.
| Feature | Transitions | Animations |
|---|---|---|
| Trigger | Requires state change | Automatic or triggered |
| Steps | 2 (start → end) | Multiple keyframes |
| Loop | No | Yes |
| Control | Limited | Full control |
@keyframes define the animation sequence with multiple steps.
/* Using from and to */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Using percentages */
@keyframes slide {
0% {
transform: translateX(0);
}
50% {
transform: translateX(100px);
}
100% {
transform: translateX(0);
}
}
/* Multiple properties */
@keyframes scaleAndRotate {
0% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
50% {
transform: scale(1.5) rotate(180deg);
opacity: 0.5;
}
100% {
transform: scale(1) rotate(360deg);
opacity: 1;
}
}
/* Complex animation with many steps */
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
25% {
transform: translateY(-30px);
}
50% {
transform: translateY(-15px);
}
75% {
transform: translateY(-5px);
}
}
/* Color progression */
@keyframes rainbow {
0% { background-color: red; }
16% { background-color: orange; }
33% { background-color: yellow; }
50% { background-color: green; }
66% { background-color: blue; }
83% { background-color: indigo; }
100% { background-color: violet; }
}
/* Reference the @keyframes name */
.element {
animation-name: fadeIn;
animation-name: bounce;
animation-name: slideIn;
}
/* How long the animation takes */
.element {
animation-duration: 1s; /* 1 second */
animation-duration: 500ms; /* 500 milliseconds */
animation-duration: 2.5s; /* 2.5 seconds */
}
/* Speed curve of animation */
.element {
animation-timing-function: ease; /* Default */
animation-timing-function: linear; /* Constant */
animation-timing-function: ease-in; /* Slow start */
animation-timing-function: ease-out; /* Slow end */
animation-timing-function: ease-in-out; /* Slow start & end */
animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Delay before animation starts */
.element {
animation-delay: 0s; /* No delay */
animation-delay: 0.5s; /* 500ms delay */
animation-delay: 1s; /* 1 second delay */
animation-delay: -0.5s; /* Start partway through */
}
/* How many times to repeat */
.element {
animation-iteration-count: 1; /* Once (default) */
animation-iteration-count: 3; /* 3 times */
animation-iteration-count: infinite; /* Forever */
}
/* Direction of animation */
.element {
animation-direction: normal; /* 0% → 100% */
animation-direction: reverse; /* 100% → 0% */
animation-direction: alternate; /* 0% → 100% → 0% → 100% */
animation-direction: alternate-reverse; /* 100% → 0% → 100% → 0% */
}
/* Style before/after animation */
.element {
animation-fill-mode: none; /* Default - no style applied */
animation-fill-mode: forwards; /* Keep final keyframe styles */
animation-fill-mode: backwards; /* Apply first keyframe before start */
animation-fill-mode: both; /* Apply both forwards & backwards */
}
/* Control play/pause */
.element {
animation-play-state: running; /* Default */
animation-play-state: paused; /* Pause animation */
}
/* Pause on hover */
.element:hover {
animation-play-state: paused;
}
Combine all animation properties in one declaration.
/* Shorthand syntax */
/* name | duration | timing-function | delay | iteration | direction | fill-mode | play-state */
.element {
animation: fadeIn 1s ease 0s 1 normal forwards running;
}
/* Simplified (common pattern) */
.element {
animation: slideIn 0.5s ease-out;
}
/* Infinite loop */
.element {
animation: spin 2s linear infinite;
}
/* With delay */
.element {
animation: bounce 1s ease 0.5s;
}
/* Multiple animations (comma-separated) */
.element {
animation: fadeIn 1s ease,
slideUp 1s ease 0.5s,
bounce 2s ease 1s infinite;
}
animation: name 1s;
animation: name 2s infinite;
animation: name 1s ease-in-out forwards;
animation: name 0.5s ease 0.3s;
Bounce
Spin
Pulse
Shake
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loader {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.6s ease-out forwards;
}
@keyframes bounceIn {
0% {
transform: scale(0);
opacity: 0;
}
50% {
transform: scale(1.2);
opacity: 1;
}
100% {
transform: scale(1);
}
}
.bounce-in {
animation: bounceIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
@keyframes slideInLeft {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.slide-in {
animation: slideInLeft 0.5s ease-out forwards;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.7;
}
}
.pulse {
animation: pulse 2s ease-in-out infinite;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }
20%, 40%, 60%, 80% { transform: translateX(10px); }
}
.shake {
animation: shake 0.5s ease;
}
@keyframes typing {
from { width: 0; }
to { width: 100%; }
}
@keyframes blink {
50% { border-color: transparent; }
}
.typewriter {
width: 0;
overflow: hidden;
border-right: 2px solid;
white-space: nowrap;
animation: typing 3s steps(40) forwards,
blink 0.75s step-end infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
}
.floating {
animation: float 3s ease-in-out infinite;
}
@keyframes ring {
0%, 100% { transform: rotate(0deg); }
10%, 30% { transform: rotate(-10deg); }
20%, 40% { transform: rotate(10deg); }
}
.notification-badge {
position: relative;
}
.notification-badge::after {
content: "3";
position: absolute;
top: -8px;
right: -8px;
background: red;
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
animation: ring 2s ease infinite;
}
@keyframes progress {
from { width: 0; }
to { width: 100%; }
}
.progress-bar {
height: 20px;
background: #f3f3f3;
border-radius: 10px;
overflow: hidden;
}
.progress-bar::after {
content: "";
display: block;
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
animation: progress 2s ease-out forwards;
}
@keyframes shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 1000px 100%;
animation: shimmer 2s infinite;
}
@keyframes modalSlideDown {
from {
transform: translateY(-100px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal {
animation: modalSlideDown 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.3); }
28% { transform: scale(1); }
42% { transform: scale(1.3); }
70% { transform: scale(1); }
}
.heartbeat {
animation: heartbeat 1.5s ease-in-out infinite;
}
/* Delay each item progressively */
.list-item {
animation: slideIn 0.5s ease-out backwards;
}
.list-item:nth-child(1) { animation-delay: 0.1s; }
.list-item:nth-child(2) { animation-delay: 0.2s; }
.list-item:nth-child(3) { animation-delay: 0.3s; }
.list-item:nth-child(4) { animation-delay: 0.4s; }
/* Or use calculated delay */
.list-item:nth-child(n) {
animation-delay: calc(0.1s * var(--i));
}
/* Run animations in sequence */
.element {
animation:
fadeIn 0.5s ease-out,
slideUp 0.5s ease-out 0.5s,
bounce 0.5s ease-out 1s;
}
// Add animation class
element.classList.add('animated');
// Listen for animation end
element.addEventListener('animationend', () => {
console.log('Animation completed!');
element.classList.remove('animated');
});
// Listen for animation iteration
element.addEventListener('animationiteration', () => {
console.log('Animation looped!');
});
/* Use CSS variables */
:root {
--animation-duration: 2s;
--animation-color: #3498db;
}
@keyframes colorPulse {
0%, 100% { background: var(--animation-color); }
50% { background: transparent; }
}
.element {
animation: colorPulse var(--animation-duration) infinite;
}
/* ✅ Good Performance */
@keyframes slide {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
/* ❌ Poor Performance */
@keyframes slide {
from { left: 0; }
to { left: 100px; }
}
/* Use will-change for complex animations */
.complex-animation {
will-change: transform, opacity;
animation: complexMove 2s ease;
}
/* Remove will-change after animation */
.complex-animation.done {
will-change: auto;
}
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}
/* Or disable completely */
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
}
}
/* Provide alternative for essential animations */
@media (prefers-reduced-motion: reduce) {
.loading {
animation: none;
}
.loading::after {
content: "Loading...";
}
}
Q1: What is the correct syntax for defining keyframes?
Q2: Which value makes an animation repeat forever?
Q3: What does animation-fill-mode: forwards do?
Q4: Which direction value makes an animation alternate back and forth?
Q5: What are the best properties to animate for performance?
animation: name duration timing delay iteration direction fill-modeAnimations provide precise control over multiple keyframe steps, unlike transitions which only have start and end states.
Use animation-iteration-count: infinite for continuous animations like loading spinners and pulsing effects.
Always animate transform and opacity for best performance. Avoid animating layout properties like width, height, or position.
Respect prefers-reduced-motion to ensure animations don't cause discomfort for users with motion sensitivity.