CSS Animations

CSS animations make elements move, fade, grow, spin, or change over time — automatically, without user interaction needed. Unlike transitions that animate between two states, animations can have multiple steps, loop indefinitely, and run the moment the page loads.

Animations vs Transitions

DIAGRAM — Transition vs Animation:

Transition:
  Needs a trigger (hover, focus, class change)
  Animates between two states only
  Runs once per trigger

Animation:
  Can run automatically on page load
  Can have any number of steps
  Can loop, reverse, pause, and fill

Two Steps to Create an Animation

Every CSS animation requires two things working together:

  1. A @keyframes block — defines what changes and when
  2. An animation property on the element — tells the element which keyframes to use and how

Step 1 — Define @keyframes

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

from is shorthand for 0% (start). to is shorthand for 100% (end). You can also use percentages for more steps.

@keyframes bounce {
  0%   { transform: translateY(0);     }
  30%  { transform: translateY(-30px); }
  50%  { transform: translateY(0);     }
  70%  { transform: translateY(-15px); }
  100% { transform: translateY(0);     }
}
DIAGRAM — Keyframe timeline:

Time:    0%    30%      50%    70%    100%
         ↓      ↓        ↓      ↓       ↓
Y pos:   0   -30px       0   -15px      0

Browser fills in all the frames between these stops automatically.

Step 2 — Apply the animation Property

/* Shorthand: name | duration | timing | delay | iterations | direction | fill-mode */
.hero-text {
  animation: fadeIn 0.6s ease-out 0s 1 normal forwards;
}

/* Most common — just name and duration */
.card {
  animation: fadeIn 0.5s ease-out;
}

animation Sub-Properties

PropertyWhat It DoesCommon Values
animation-nameWhich @keyframes to usefadeIn, bounce, spin
animation-durationHow long one cycle takes0.3s, 1s, 500ms
animation-timing-functionSpeed curveease, linear, ease-out
animation-delayWait before starting0s, 0.2s, -0.5s
animation-iteration-countHow many times to play1, 3, infinite
animation-directionForward, backward, or alternatenormal, reverse, alternate
animation-fill-modeState before/after animationnone, forwards, backwards, both
animation-play-stateRunning or pausedrunning, paused

animation-iteration-count — Looping

/* Play once */
animation: spin 1s linear 1;

/* Play 3 times */
animation: pulse 0.5s ease 3;

/* Loop forever */
animation: spin 2s linear infinite;

animation-direction — Reversing

DIAGRAM — animation-direction options:

normal:            A → B  A → B  A → B  (always forward)
reverse:           B → A  B → A  B → A  (always backward)
alternate:         A → B  B → A  A → B  (ping-pong)
alternate-reverse: B → A  A → B  B → A  (ping-pong, starts backward)
@keyframes breathe {
  from { transform: scale(1);   }
  to   { transform: scale(1.1); }
}

.pulse-icon {
  animation: breathe 1.5s ease-in-out infinite alternate;
  /* Grows and shrinks smoothly, forever */
}

animation-fill-mode — State Before and After

Fill mode controls what styles the element has before the animation starts and after it ends.

DIAGRAM — animation-fill-mode:

Element starts at opacity: 1 (no fill)
@keyframes: from { opacity: 0 } to { opacity: 1 }

none (default):
  [opacity: 1] → [animation: 0→1] → [opacity: 1 again]
  Element flickers back to original after animation

forwards:
  [opacity: 1] → [animation: 0→1] → [stays at opacity: 1]
  Holds the final keyframe state ← most commonly needed

backwards:
  [opacity: 0 during delay] → [animation: 0→1] → [back to original]
  Applies FROM state during delay period

both:
  Combines forwards + backwards behavior
/* Fade in and stay visible — use forwards */
.popup {
  opacity: 0;
  animation: fadeIn 0.5s ease-out forwards;
}

animation-delay — Start Later

/* Start after 1 second */
.card { animation: slideIn 0.5s 1s ease forwards; }

/* Negative delay — start mid-animation */
.spinner { animation: spin 2s linear -0.5s infinite; }
/* Starting at -0.5s means it begins halfway through its first cycle */

Staggered Animations with Delay

.item { opacity: 0; animation: fadeIn 0.4s ease 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;  }
DIAGRAM — Staggered card entrance:

t=0.0s:  [Card 1 fades in]
t=0.1s:              [Card 2 fades in]
t=0.2s:                          [Card 3 fades in]
t=0.3s:                                      [Card 4 fades in]

Creates a cascading wave effect.

animation-play-state — Pause and Resume

.spinner {
  animation: spin 1s linear infinite;
}

.spinner:hover {
  animation-play-state: paused; /* pauses on hover */
}

Multiple Animations on One Element

Separate multiple animations with commas.

@keyframes fadeIn  { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideUp { from { transform: translateY(20px); } to { transform: translateY(0); } }

.hero {
  animation:
    fadeIn  0.5s ease forwards,
    slideUp 0.5s ease forwards;
}

Common Animation Examples

Loading Spinner

@keyframes spin {
  to { transform: rotate(360deg); }
}

.loader {
  width: 40px;
  height: 40px;
  border: 4px solid #f0f0f0;
  border-top-color: #3498db;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

Pulsing Dot

@keyframes pulse {
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%       { transform: scale(1.4); opacity: 0.6; }
}

.dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: green;
  animation: pulse 1.5s ease-in-out infinite;
}

Typing Cursor Blink

@keyframes blink {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0; }
}

.cursor::after {
  content: "|";
  animation: blink 1s step-end infinite;
}

Shake / Error Effect

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%       { transform: translateX(-8px); }
  40%       { transform: translateX(8px); }
  60%       { transform: translateX(-6px); }
  80%       { transform: translateX(6px); }
}

.error {
  animation: shake 0.5s ease;
}

Respecting Reduced Motion

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

Always include this. Some users experience dizziness or nausea from motion-heavy animations. The OS reduced-motion setting is their signal to turn it off.

Performance — GPU-Safe Properties

DIAGRAM — Which properties animate smoothly:

✅ Animate these freely (GPU-accelerated):
   transform (translate, scale, rotate)
   opacity

⚠ Use with caution (triggers layout recalc each frame):
   width, height, top, left, margin, padding, font-size

Always animate transform and opacity first. If you need to move an element, use transform: translateX(), not left:.

CSS animations handle everything from loading spinners to page entrance effects to complex multi-step sequences. Start with a two-keyframe from/to animation, get comfortable with animation-fill-mode: forwards and infinite loops, and you will cover every common animation pattern on the web.

Leave a Comment

Your email address will not be published. Required fields are marked *