CSS Animations
CSS animations create complex motion sequences using @keyframes that define animation states at different points in time. Unlike transitions (which animate between two states), animations can have unlimited keyframes and repeat infinitely. Animations are ideal for looping effects, complex sequences, and visual storytelling.
Defining keyframes
/* Define animation with @keyframes */
@keyframes slide-in {
0% {
transform: translateX(-100%);
opacity: 0;
}
50% {
opacity: 1;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
/* Use the animation */
.element {
animation: slide-in 1s ease-out;
}
/* Alternative: from and to */
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fading {
animation: fade 0.5s ease-in;
}
/* Multiple keyframes */
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
}
.bouncing {
animation: bounce 1s ease-in-out infinite;
}Animation properties
Property | Purpose | Example |
|---|---|---|
| Name of @keyframes | @keyframes my-animation |
| How long the animation lasts | 1s, 500ms |
| Easing curve | ease, linear, ease-in-out |
| Delay before starting | 0.5s, 200ms |
| How many times to repeat | 1, 5, infinite |
| Reverse or alternate | normal, reverse, alternate |
| State after animation | forwards, backwards, both |
| Running or paused | running, paused |
/* Longhand */
.element {
animation-name: slide-in;
animation-duration: 1s;
animation-timing-function: ease-out;
animation-delay: 0.2s;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-fill-mode: forwards;
}
/* Shorthand: name duration timing delay iteration direction fill */
.element {
animation: slide-in 1s ease-out 0.2s infinite alternate forwards;
}
/* Multiple animations */
.element {
animation:
slide-in 1s ease-out,
fade-in 0.5s ease-in 0.5s;
}
/* Control animation playback */
.element {
animation-play-state: paused;
}
.element:hover {
animation-play-state: running;
}Animation direction and fill mode
/* animation-direction */
@keyframes move-right {
0% { transform: translateX(0); }
100% { transform: translateX(100px); }
}
.normal {
animation: move-right 2s infinite;
animation-direction: normal; /* forward each time */
}
.reverse {
animation: move-right 2s infinite;
animation-direction: reverse; /* backward each time */
}
.alternate {
animation: move-right 2s infinite;
animation-direction: alternate; /* forward, backward, forward... */
}
.alternate-reverse {
animation: move-right 2s infinite;
animation-direction: alternate-reverse; /* backward first */
}
/* animation-fill-mode: what happens after animation */
@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
.forwards {
animation: fade 1s forwards;
/* Stays in final state (opacity: 1) after animation ends */
}
.backwards {
animation: fade 1s backwards;
/* Jumps back to start state (opacity: 0) after animation ends */
}
.both {
animation: fade 1s both;
/* Applies both directions */
}
.none {
animation: fade 1s none;
/* Returns to original state, no fill */
}Practical animation examples
<!-- Infinite loading spinner -->
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #0066cc;
border-radius: 50%;
animation: spin 1s linear infinite;
}
<!-- Pulse animation -->
@keyframes pulse {
0%, 100% {
opacity: 1;
box-shadow: 0 0 0 0 rgba(0, 102, 204, 0.7);
}
50% {
opacity: 0.8;
box-shadow: 0 0 0 10px rgba(0, 102, 204, 0);
}
}
.pulse {
animation: pulse 2s infinite;
}
<!-- Bounce animation -->
@keyframes bounce {
0%, 100% { transform: translateY(0); }
25% { transform: translateY(-20px); }
50% { transform: translateY(0); }
75% { transform: translateY(-10px); }
}
.bouncing-ball {
width: 20px;
height: 20px;
background: blue;
border-radius: 50%;
animation: bounce 1s ease-in-out infinite;
}
<!-- Slide in animation -->
@keyframes slide-in {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.slide-in {
animation: slide-in 0.5s ease-out forwards;
}
<!-- Gradient animation -->
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.gradient-bg {
background: linear-gradient(45deg, #667eea, #764ba2, #f093fb);
background-size: 200% 200%;
animation: gradient-shift 3s ease infinite;
}Animation timing and delays
/* Stagger animations with delays */
.item {
animation: fade-in 0.5s ease-out forwards;
}
.item:nth-child(1) { animation-delay: 0s; }
.item:nth-child(2) { animation-delay: 0.1s; }
.item:nth-child(3) { animation-delay: 0.2s; }
.item:nth-child(4) { animation-delay: 0.3s; }
<!-- Items fade in one after another -->
/* CSS custom property for delays */
.item {
animation: fade-in 0.5s ease-out forwards;
animation-delay: calc(var(--index) * 100ms);
}
/* Then in HTML: <div class="item" style="--index: 0;"> ... </div> */
/* Dynamic timing based on state */
.element {
animation-duration: 0.3s;
animation-timing-function: ease-out;
}
.element.fast {
animation-duration: 0.1s;
}
.element.slow {
animation-duration: 1s;
}Animation performance
/* Optimize with transforms */
@keyframes move {
from { transform: translateX(0); } /* fast, GPU-accelerated */
to { transform: translateX(100px); }
}
@keyframes bad-move {
from { left: 0; } /* slow, triggers layout */
to { left: 100px; }
}
/* Use opacity for fading (fast) */
@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
/* Optimize by limiting affected elements */
.animated {
will-change: transform;
/* Tell browser to optimize for this */
}
/* Reduce animations on slow devices */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}