Skip to content

CSS Animations: Creating Smooth Keyframe Animations

Movies aren't static images but flowing visuals formed by playing 24 frames of images per second. CSS animations use the same principle—by defining keyframes, elements can smoothly transition between multiple states. Unlike transitions, which can only change between two states when triggered, animations can create complex multi-step animations that play automatically without user interaction, making them core tools for loading indicators, decorative effects, and entrance animations.

Animation vs Transition: When to Use Which?

Before learning animations, understand their differences from transitions:

FeatureTransitionAnimation
TriggerRequires state change (e.g., hover)Plays automatically, no trigger needed
ControlOnly start and end pointsMultiple keyframes can be defined
LoopingDoesn't supportSupports infinite looping
DirectionOnly forward or reverseSupports alternate, reverse, etc.
Pause/ResumeNot supportedSupported
Use CasesInteractive feedback (buttons, links)Continuous animations (loaders, decorations)

Simple judgment:

  • User-triggered simple effects → Transition
  • Auto-play or complex multi-step → Animation

@keyframes: Defining Animation Sequences

@keyframes rules define keyframes of an animation on the timeline, with each keyframe describing the element's style at that moment.

Basic Syntax

css
/* Define animation named slidein */
@keyframes slidein {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }

  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Use percentages for more precise timing points */
@keyframes fadeInOut {
  0% {
    opacity: 0;
  }

  50% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}
  • from equals 0% (animation start)
  • to equals 100% (animation end)
  • Can define any percentage keyframe (e.g., 25%, 33.33%, 75%)

Multi-Property Keyframes

Each keyframe can contain multiple CSS properties.

css
@keyframes complexAnimation {
  0% {
    transform: translateX(0) rotate(0deg) scale(1);
    background-color: #3498db;
    opacity: 0.5;
  }

  50% {
    transform: translateX(100px) rotate(180deg) scale(1.5);
    background-color: #e74c3c;
    opacity: 1;
  }

  100% {
    transform: translateX(0) rotate(360deg) scale(1);
    background-color: #2ecc71;
    opacity: 0.5;
  }
}

Same Style at Multiple Keyframes

Multiple time points can share styles, separated by commas.

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

  50% {
    transform: scale(1.1);
    opacity: 0.8;
  }
}

Animation Properties: Controlling Animation Playback

After defining keyframes, use animation properties to apply them to elements.

animation-name: Animation Name

Specify which @keyframes to use.

css
.box {
  animation-name: slidein;
}

animation-duration: Animation Length

Time for one complete animation cycle.

css
.box {
  animation-duration: 2s; /* 2 seconds */
}

.fast-animation {
  animation-duration: 500ms; /* 0.5 seconds */
}

animation-timing-function: Easing Function

Controls the animation's speed curve, same as transition-timing-function.

css
.linear {
  animation-timing-function: linear; /* Uniform speed */
}

.ease {
  animation-timing-function: ease; /* Slow-fast-slow (default) */
}

.ease-in-out {
  animation-timing-function: ease-in-out; /* Slow start and end */
}

.custom {
  animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* Bouncing effect */
}

Note: Easing functions apply between every two keyframes, not the entire animation.

animation-delay: Start Delay

Wait time before animation starts.

css
.delayed {
  animation-delay: 1s; /* Start 1 second later */
}

.negative-delay {
  animation-delay: -1s; /* Negative: skip first 1s, start from middle */
}

animation-iteration-count: Play Count

Number of times animation repeats.

css
.play-once {
  animation-iteration-count: 1; /* Play 1 time (default) */
}

.play-three-times {
  animation-iteration-count: 3; /* Play 3 times */
}

.loop-forever {
  animation-iteration-count: infinite; /* Infinite loop */
}

.fractional {
  animation-iteration-count: 2.5; /* Play 2.5 times */
}

animation-direction: Play Direction

Controls whether animation plays forward, backward, or alternates.

css
.normal {
  animation-direction: normal;
  /* Always 0% → 100% (default) */
}

.reverse {
  animation-direction: reverse;
  /* Always 100% → 0% */
}

.alternate {
  animation-direction: alternate;
  /* Odd iterations forward (0%→100%), even backward (100%→0%) */
}

.alternate-reverse {
  animation-direction: alternate-reverse;
  /* Odd iterations backward, even forward */
}

Practical use: alternate with infinite can create back-and-forth swinging effects.

animation-fill-mode: Fill Mode

Controls element's style before and after animation.

css
.none-fill {
  animation-fill-mode: none;
  /* Default: return to original style after animation ends */
}

.forwards {
  animation-fill-mode: forwards;
  /* Keep last frame style (100% or 0% depending on direction) */
}

.backwards {
  animation-fill-mode: backwards;
  /* Apply first frame style during delay */
}

.both {
  animation-fill-mode: both;
  /* Apply both forwards and backwards */
}

Commonly used: forwards makes element maintain animation end state.

animation-play-state: Play/Pause

Controls whether animation is playing or paused.

css
.running {
  animation-play-state: running; /* Playing (default) */
}

.paused {
  animation-play-state: paused; /* Paused */
}

/* Practical application: pause on hover */
.animation-box {
  animation: spin 2s linear infinite;
}

.animation-box:hover {
  animation-play-state: paused;
}

animation Shorthand Property

In development, shorthand form is more commonly used:

css
/* Syntax: name duration timing-function delay iteration-count direction fill-mode play-state */
.box {
  animation: slidein 2s ease-in-out 0.5s infinite alternate both running;
}

/* Common simplified version */
.box {
  animation: slidein 2s infinite;
}

/* Multiple animations, comma-separated */
.box {
  animation: slidein 2s, fadeIn 1s 0.5s, rotate 3s infinite;
}

Order tip: First time value is duration, second is delay.

Practical Applications: Common Animation Effects

Loading Indicator: Rotating Circle

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

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

Pulse Effect

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

  50% {
    transform: scale(1.05);
    opacity: 0.7;
  }
}

.pulse-button {
  animation: pulse 2s ease-in-out infinite;
}

Bounce Animation

css
@keyframes bounce {
  0%,
  20%,
  50%,
  80%,
  100% {
    transform: translateY(0);
  }

  40% {
    transform: translateY(-30px);
  }

  60% {
    transform: translateY(-15px);
  }
}

.bounce-element {
  animation: bounce 2s infinite;
}

Fade In and Out

css
@keyframes fadeInOut {
  0%,
  100% {
    opacity: 0;
  }

  50% {
    opacity: 1;
  }
}

.fade-text {
  animation: fadeInOut 3s infinite;
}

Slide In Effect

css
@keyframes slideInLeft {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }

  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.slide-in {
  animation: slideInLeft 0.5s ease-out forwards;
}

Shake Effect

css
@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }

  10%,
  30%,
  50%,
  70%,
  90% {
    transform: translateX(-10px);
  }

  20%,
  40%,
  60%,
  80% {
    transform: translateX(10px);
  }
}

.shake-on-error {
  animation: shake 0.5s;
}

Typewriter Effect

css
@keyframes typing {
  from {
    width: 0;
  }
  to {
    width: 100%;
  }
}

@keyframes blink {
  50% {
    border-color: transparent;
  }
}

.typewriter {
  width: 0;
  overflow: hidden;
  white-space: nowrap;
  border-right: 3px solid #333;
  animation: typing 3.5s steps(40) 1s forwards, blink 0.75s step-end infinite;
}

Practical Case Study: Complex Loading Animation

Create a multi-dot bouncing loading indicator.

html
<div class="loader">
  <div class="dot"></div>
  <div class="dot"></div>
  <div class="dot"></div>
</div>
css
@keyframes bounce {
  0%,
  80%,
  100% {
    transform: scale(0);
    opacity: 0.5;
  }

  40% {
    transform: scale(1);
    opacity: 1;
  }
}

.loader {
  display: flex;
  gap: 10px;
  justify-content: center;
  align-items: center;
  height: 100px;
}

.dot {
  width: 15px;
  height: 15px;
  background: #3498db;
  border-radius: 50%;
  animation: bounce 1.4s infinite ease-in-out both;
}

/* Stagger animation timing */
.dot:nth-child(1) {
  animation-delay: -0.32s;
}

.dot:nth-child(2) {
  animation-delay: -0.16s;
}

.dot:nth-child(3) {
  animation-delay: 0s;
}

Practical Case Study: Progress Bar Animation

A filling progress bar.

html
<div class="progress-container">
  <div class="progress-bar"></div>
</div>
css
@keyframes fillProgress {
  from {
    width: 0%;
  }
  to {
    width: 100%;
  }
}

.progress-container {
  width: 100%;
  height: 30px;
  background: #ecf0f1;
  border-radius: 15px;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  background: linear-gradient(90deg, #3498db, #2ecc71);
  border-radius: 15px;
  animation: fillProgress 3s ease-out forwards;
}

Practical Case Study: Floating Icon

Create an icon that floats up and down.

css
@keyframes float {
  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-20px);
  }
}

@keyframes floatShadow {
  0%,
  100% {
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
  }

  50% {
    box-shadow: 0 15px 25px rgba(0, 0, 0, 0.1);
  }
}

.floating-icon {
  width: 60px;
  height: 60px;
  background: #3498db;
  border-radius: 50%;
  animation: float 3s ease-in-out infinite, floatShadow 3s ease-in-out infinite;
}

Practical Case Study: Card Flip Animation

Auto-flipping card.

html
<div class="auto-flip-card">
  <div class="card-inner">
    <div class="card-front">Front</div>
    <div class="card-back">Back</div>
  </div>
</div>
css
@keyframes flip {
  0%,
  45% {
    transform: rotateY(0deg);
  }

  50%,
  95% {
    transform: rotateY(180deg);
  }

  100% {
    transform: rotateY(360deg);
  }
}

.auto-flip-card {
  width: 200px;
  height: 300px;
  perspective: 1000px;
}

.card-inner {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  animation: flip 6s infinite;
}

.card-front,
.card-back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  font-weight: bold;
  border-radius: 12px;
}

.card-front {
  background: linear-gradient(135deg, #667eea, #764ba2);
  color: white;
}

.card-back {
  background: linear-gradient(135deg, #f093fb, #f5576c);
  color: white;
  transform: rotateY(180deg);
}

Combining Multiple Animations

An element can run multiple animations simultaneously.

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

@keyframes colorChange {
  0% {
    background: #3498db;
  }
  50% {
    background: #e74c3c;
  }
  100% {
    background: #2ecc71;
  }
}

@keyframes scale {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.2);
  }
}

.multi-animation {
  animation: spin 3s linear infinite, colorChange 6s ease-in-out infinite,
    scale 2s ease-in-out infinite;
}

Note: If multiple animations modify the same property (like transform), later ones will override earlier ones. Need to combine them into one @keyframes.

JavaScript Animation Control

You can dynamically control animations with JavaScript.

javascript
const element = document.querySelector(".box");

// Add animation
element.style.animation = "slidein 2s ease-out forwards";

// Remove animation
element.style.animation = "none";

// Pause animation
element.style.animationPlayState = "paused";

// Resume animation
element.style.animationPlayState = "running";

// Listen for animation events
element.addEventListener("animationstart", () => {
  console.log("Animation started");
});

element.addEventListener("animationiteration", () => {
  console.log("Animation iteration");
});

element.addEventListener("animationend", () => {
  console.log("Animation ended");
});

Performance Optimization and Best Practices

Prioritize Transform and Opacity

css
/* ❌ Low performance: triggers reflow/repaint */
@keyframes badAnimation {
  from {
    width: 100px;
    height: 100px;
    left: 0;
  }
  to {
    width: 200px;
    height: 200px;
    left: 100px;
  }
}

/* ✅ High performance: only triggers compositing */
@keyframes goodAnimation {
  from {
    transform: scale(1) translateX(0);
    opacity: 1;
  }
  to {
    transform: scale(2) translateX(100px);
    opacity: 0.5;
  }
}

Reduce Number of Animated Elements

css
/* ⚠️ Hundreds of elements animating simultaneously will cause lag */
.many-items {
  animation: complex 2s infinite;
}

/* ✅ Only animate necessary elements */
.featured-item {
  animation: highlight 2s infinite;
}

Use will-change

css
.heavy-animation {
  will-change: transform, opacity;
  animation: complexMove 2s infinite;
}

Control Frame Rate: Use steps()

For sprite animations, use steps() instead of smooth transitions.

css
@keyframes spriteAnimation {
  to {
    background-position: -480px 0; /* Assume 8 frames, 60px each */
  }
}

.sprite {
  width: 60px;
  height: 60px;
  background: url("sprite.png") no-repeat;
  animation: spriteAnimation 0.8s steps(8) infinite;
  /* 8 steps, each frame shows 0.1s */
}

Common Issues and Solutions

Issue 1: Animation Plays Immediately on Page Load

Sometimes you want users to see a state before animation starts.

css
/* Use animation-delay */
.delayed-start {
  animation: fadeIn 1s 2s forwards;
  /* Delay 2s before starting */
}

/* Or use JavaScript control */
javascript
setTimeout(() => {
  document.querySelector(".box").classList.add("animated");
}, 2000);

Issue 2: Flicker After Animation Ends

Use animation-fill-mode: forwards to maintain the last frame.

css
.fade-out {
  animation: fadeOut 1s forwards; /* Won't jump back to initial state */
}

Issue 3: Infinite Animations Consume Resources

Pause animations when not visible.

javascript
document.addEventListener("visibilitychange", () => {
  const animations = document.querySelectorAll(".animated");
  animations.forEach((el) => {
    el.style.animationPlayState = document.hidden ? "paused" : "running";
  });
});

Issue 4: Animations Not Smooth on Mobile

css
/* Add hardware acceleration */
.smooth-animation {
  transform: translate3d(0, 0, 0); /* Trigger GPU acceleration */
  will-change: transform;
  animation: move 2s infinite;
}

Accessibility Considerations

Some users may be sensitive to animations, so respect system settings.

css
/* Check if user prefers reduced motion */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Or only disable specific animations */
@media (prefers-reduced-motion: reduce) {
  .decorative-animation {
    animation: none;
  }

  .functional-animation {
    animation-duration: 0.3s; /* Keep but shorten */
  }
}

Summary

CSS animation is a powerful tool for creating dynamic, eye-catching visual experiences:

Core Concepts:

  • @keyframes: Define animation sequence keyframes
  • animation-name: Specify animation to use
  • animation-duration: Animation length
  • animation-iteration-count: Play count (can be infinite)
  • animation-direction: Play direction
  • animation-fill-mode: Before and after animation states

Best Practices:

  1. Performance first: Use transform and opacity
  2. Moderate use: Not all elements need animation
  3. Consider accessibility: Respect prefers-reduced-motion
  4. Semantic naming: Animation names should describe their effect
  5. Test performance: Verify smoothness on low-end devices

When to Use Animation:

  • Loading indicators (rotating, pulsing, progress bars)
  • Decorative animations (background patterns, floating elements)
  • Guide animations (arrow indicators, highlight tips)
  • Brand animations (logo animations, signature effects)

CSS animations bring static pages to life, but remember—animations are tools for enhancing user experience, not the goal itself. The best animations are those that enhance understanding, provide feedback, and guide attention, not those that distract or show off. Elegant animations should feel as natural as breathing—users will perceive their presence without being disturbed by them.