Difficulty: Intermediate
How do CSS animations differ from transitions? Explain @keyframes.
CSS animations use @keyframes for multi-step, auto-playing animations.
Differences from transitions: - Transitions: triggered by state change (hover, class), 2 states only (A to B) - Animations: can auto-play, loop, have multiple steps, run independently
Animation properties: - animation-name: keyframe name - animation-duration: how long - animation-timing-function: easing - animation-delay: wait before start - animation-iteration-count: number or infinite - animation-direction: normal, reverse, alternate - animation-fill-mode: forwards (keep end state), backwards, both - animation-play-state: running, paused
/* Define keyframes */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Apply animation */
.card {
animation: fadeInUp 0.5s ease-out forwards;
/* forwards: keeps the final state */
}
/* Multi-step animation */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.badge {
animation: pulse 2s ease-in-out infinite;
}
/* Shorthand */
.element {
animation: name 1s ease 0.5s 3 alternate forwards;
/* name duration timing delay count direction fill */
}
from/to is shorthand for 0%/100%. Use percentages for multi-step animations. forwards keeps the final keyframe state after the animation ends.
/* Loading spinner */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 2rem;
height: 2rem;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* Skeleton loading shimmer */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(
90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
/* Staggered entrance */
.list-item {
animation: fadeInUp 0.4s ease-out forwards;
opacity: 0;
}
.list-item:nth-child(1) { animation-delay: 0.0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
The spinner and skeleton shimmer are used in every modern web app. Staggered delays create a cascading entrance effect.
/* GOOD: animate only transform and opacity */
@keyframes slideIn {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* BAD: animating layout properties */
@keyframes badSlide {
from { margin-left: -100%; } /* triggers layout */
to { margin-left: 0; }
}
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Pause on hover (for autoplaying animations) */
.carousel {
animation: scroll 10s linear infinite;
}
.carousel:hover {
animation-play-state: paused;
}
Always include prefers-reduced-motion for users with motion sensitivity. Only animate transform and opacity for smooth 60fps performance.
Keyframes, Animation Properties, Performance, Multi-step Animations, Accessibility