Advertisement
✨ CSS Effects

CSS Animations – @keyframes and the Animation API

While transitions animate between two states triggered by a user interaction, CSS animations run independently — they can start on page load, loop forever, move through multiple steps, and play in reverse. CSS animations are built from two parts: the @keyframes rule that defines the motion, and the animation properties that control playback. In this lesson you will learn to write complex multi-step animations, control every aspect of playback, and build the most common animation patterns used in real products.

⏱️ 22 min read 🎯 Beginner–Intermediate 📅 Updated 2026 👁️ Lesson 2 of 4

@keyframes – Defining the Motion

A @keyframes rule defines a named animation with waypoints (keyframes) that specify what CSS looks like at each point in the animation timeline.

CSS – @keyframes syntax
/* from / to — two-step animation */
@keyframes fade-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Percentage waypoints — multi-step */
@keyframes bounce {
  0%   { transform: translateY(0);    }
  30%  { transform: translateY(-24px); }
  60%  { transform: translateY(-12px); }
  80%  { transform: translateY(-6px);  }
  100% { transform: translateY(0);    }
}

/* Multiple properties at once */
@keyframes hero-entrance {
  0%   { opacity: 0; transform: scale(0.8) translateY(40px); }
  60%  { opacity: 1; transform: scale(1.02) translateY(-4px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}

/* Rules can share keyframes */
@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%       { transform: scale(1.05); opacity: 0.85; }
}
▶ Live animation demos
Pulse
Fade In

The Animation Properties

animation-name

CSS
animation-name: fade-in;       /* matches @keyframes name */
animation-name: none;          /* disables animation */
animation-name: spin, pulse;   /* multiple animations */

animation-duration

CSS
animation-duration: 0.6s;    /* how long one cycle takes */
animation-duration: 1500ms;  /* same as 1.5s */

animation-timing-function

Same values as transition-timing-function: ease, linear, ease-in, ease-out, ease-in-out, cubic-bezier(), steps().

animation-delay

CSS
animation-delay: 0s;      /* starts immediately */
animation-delay: 0.5s;    /* waits 0.5s before starting */
animation-delay: -1s;     /* starts 1s INTO the animation (skip beginning) */

animation-iteration-count

CSS
animation-iteration-count: 1;         /* play once (default) */
animation-iteration-count: 3;         /* play 3 times */
animation-iteration-count: infinite;  /* loop forever */
animation-iteration-count: 2.5;       /* 2.5 cycles — ends halfway through */

animation-direction

CSS
animation-direction: normal;            /* default — plays forward */
animation-direction: reverse;           /* plays backward */
animation-direction: alternate;         /* forward, then backward, then forward… */
animation-direction: alternate-reverse; /* backward, then forward, then backward… */

animation-fill-mode

Controls what styles apply before and after the animation runs — one of the most misunderstood properties.

CSS – fill-mode
animation-fill-mode: none;      /* default — no styles held */
animation-fill-mode: forwards;  /* keep final keyframe styles after animation ends */
animation-fill-mode: backwards; /* apply from keyframe styles during delay period */
animation-fill-mode: both;      /* apply backwards + forwards — almost always what you want */

/* Example: fade-in that stays visible after finishing */
.card {
  opacity: 0;   /* start invisible */
  animation: fade-in 0.6s ease-out 0.2s both;
  /* 'both' means:
     - during delay (0.2s): apply 'from' styles (opacity: 0) — no flash of visible
     - after ending: keep 'to' styles (opacity: 1) — stays visible */
}

animation-play-state

CSS
animation-play-state: running;  /* default — animation plays */
animation-play-state: paused;   /* pause the animation */

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

/* Controlled by JS class */
.anim-paused { animation-play-state: paused; }
Advertisement

animation Shorthand

CSS – animation shorthand
/* animation: name duration timing-function delay iteration-count direction fill-mode */
animation: fade-in 0.6s ease-out 0.2s 1 normal both;

/* Most common shorthand patterns */
.spinner { animation: spin 0.8s linear infinite; }
.pulse   { animation: pulse 2s ease-in-out infinite; }
.entrance { animation: fade-in 0.5s ease-out both; }
.stagger  { animation: slide-up 0.4s ease-out 0.1s both; }

/* Multiple animations on one element */
.element {
  animation:
    fade-in  0.5s ease-out both,
    float    3s ease-in-out 0.5s infinite;
}

Real-World Animation Patterns

CSS – Practical animations
/* 1. Loading spinner */
@keyframes spin {
  to { transform: rotate(360deg); }
}
.spinner {
  width: 40px; height: 40px;
  border: 4px solid #e0e0e0;
  border-top-color: #1572B6;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

/* 2. Skeleton loader (shimmer) */
@keyframes shimmer {
  0%   { background-position: -400px 0; }
  100% { background-position:  400px 0; }
}
.skeleton {
  background: linear-gradient(90deg, #e0e0e0 25%, #f5f5f5 50%, #e0e0e0 75%);
  background-size: 800px 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

/* 3. Page entrance (stagger children) */
@keyframes slide-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}
.card { animation: slide-up 0.4s ease-out both; }
.card:nth-child(1) { animation-delay: 0s; }
.card:nth-child(2) { animation-delay: 0.08s; }
.card:nth-child(3) { animation-delay: 0.16s; }
.card:nth-child(4) { animation-delay: 0.24s; }

/* 4. Attention pulse */
@keyframes attention {
  0%, 100% { box-shadow: 0 0 0 0 rgba(21,114,182,0.4); }
  70%       { box-shadow: 0 0 0 12px rgba(21,114,182,0); }
}
.notification-dot { animation: attention 2s infinite; }

/* 5. Floating effect */
@keyframes float {
  0%, 100% { transform: translateY(0); }
  50%       { transform: translateY(-12px); }
}
.hero-icon { animation: float 3s ease-in-out infinite; }

/* 6. Typewriter cursor */
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
.cursor { animation: blink 1s step-end infinite; }

/* 7. Accessibility: always respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .spinner, .skeleton, .card, .notification-dot, .hero-icon, .cursor {
    animation: none;
  }
}
▶ Skeleton loader demo

Transition vs Animation, and the Performance Rule

Both create motion, but they answer different questions. A transition animates between two states triggered by something (hover, class change). An animation (@keyframes) runs a multi-step sequence on its own, and can loop.

/* Transition: A → B on hover */
.btn { transition: transform 0.2s; }
.btn:hover { transform: scale(1.1); }

/* Animation: multi-step, self-running, loops */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}
.dot { animation: pulse 1s infinite; }

Animate only transform and opacity

This is the one rule that separates smooth 60fps motion from janky animation. The browser can move transform and opacity on the GPU without recalculating layout. Animating width, height, top, or margin forces a reflow every frame — expensive and stuttery.

Cheap (GPU)Expensive (reflow)
transform: translate/scale/rotatetop, left, margin
opacitywidth, height

Need to move a box? Use transform: translateX(100px), not left: 100px.

🏋️ Practical Exercise

  1. Define a @keyframes animation.
  2. Apply it with the animation property.
  3. Set the duration and iteration count.
  4. Loop it with infinite.
  5. Add percentage keyframes (0%, 50%, 100%).

🔥 Challenge Exercise

Create a loading spinner with @keyframes that rotates 360° infinitely, plus a separate "pulse" animation on a badge. Control timing, iteration, and easing. Explain the difference between transitions and keyframe animations and when to use each.

📋 Summary

  • @keyframes name { } — defines the animation. Use from/to or percentage waypoints.
  • animation-name — links element to a @keyframes rule.
  • animation-duration — how long one cycle takes.
  • animation-iteration-count: infinite — loops forever.
  • animation-direction: alternate — forward then backward (ping-pong).
  • animation-fill-mode: both — apply first keyframe during delay, keep last keyframe after end. Almost always use both.
  • animation-play-state: paused — pause/resume with CSS or JS.
  • Shorthand: animation: name duration timing delay count direction fill
  • Stagger: use animation-delay with :nth-child().
  • Always add prefers-reduced-motion override.

Interview Questions

  • What is a @keyframes rule?
  • What is the difference between a transition and an animation?
  • What does animation-iteration-count: infinite do?
  • How do percentage keyframes work?
  • What does animation-fill-mode control?

FAQ

Why does my animation flash at the start? +

The element shows its natural CSS state before the animation's first keyframe applies. Fix: set animation-fill-mode: backwards or both — this applies the from keyframe styles immediately, even during the delay period. For a fade-in animation, also set opacity: 0 on the element as a backup for browsers that don't support fill-mode.

How do I restart a CSS animation? +

CSS animations don't replay once complete unless looped. To restart via JavaScript: remove the class with the animation, force a reflow (read element.offsetWidth), then re-add the class. Example: el.classList.remove('animated'); el.offsetWidth; el.classList.add('animated');. The reflow forces the browser to process the removal before re-adding, restarting the animation from the beginning.

Can I control CSS animations with JavaScript? +

Yes, several ways: (1) Toggle a CSS class that applies the animation. (2) Set animation-play-state: paused/running via element.style.animationPlayState. (3) Use the Web Animations API (element.getAnimations()) for fine-grained control — play, pause, reverse, change speed, and listen for animation events. (4) Listen to animation events: animationstart, animationend, animationiteration.