CSSanimation-iteration-count & direction

animation-iteration-count & animation-direction

animation-iteration-count controls how many times an animation repeats (1, 2, infinite, 0.5). animation-direction controls playback direction (normal, reverse, alternate, alternate-reverse). Together they create looping animations, back-and-forth effects, and ping-pong patterns. Essential for loading spinners, bouncing elements, and continuous animations.

Animation iteration count

Value

Behavior

Use Case

1 (default)

Plays once

One-time animations, entrances

2, 3, ..., N

Plays N times

Fixed repetition count

infinite

Loops forever

Spinners, loaders, continuous

0.5, 1.5, etc

Decimal counts

Partial animation (e.g., 1.5 = 1.5x plays)

CSS
/* Play once (default) */
.one-time {
  animation: slide-in 0.5s ease;
  animation-iteration-count: 1;
  /* Animates once on load */
}

/* Play multiple times */
.repeat-three {
  animation: blink 0.5s ease;
  animation-iteration-count: 3;
  /* Blinks 3 times */
}

/* Play indefinitely */
.infinite {
  animation: spin 2s linear;
  animation-iteration-count: infinite;
  /* Continuous spinning */
}

/* Partial iteration */
.partial {
  animation: move 1s ease;
  animation-iteration-count: 1.5;
  /* Plays 1.5 times (stops halfway through second iteration) */
}

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

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

@keyframes move {
  from { transform: translateX(0); }
  to { transform: translateX(100px); }
}

/* Common patterns */

/* Loading spinner */
.loader {
  animation: spin 1s linear infinite;
  /* Spins continuously */
}

/* Pulse effect */
.pulse {
  animation: pulse 2s ease-in-out infinite;
}

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

/* Flash warning */
.warning-flash {
  animation: flash 0.3s ease;
  animation-iteration-count: 3;
  /* Flashes 3 times then stops */
}

@keyframes flash {
  0%, 50%, 100% { opacity: 1; }
  25%, 75% { opacity: 0.3; }
}
Animation direction

Value

Behavior

Effect

normal (default)

Forward each time

Plays 0% → 100%, then 0% → 100%

reverse

Backward each time

Plays 100% → 0%, then 100% → 0%

alternate

Back and forth

Plays 0% → 100% → 0% → 100%...

alternate-reverse

Backward back-forth

Plays 100% → 0% → 100% → 0%...

CSS
/* Normal: forward each iteration */
.normal-direction {
  animation: slide 1s ease;
  animation-iteration-count: 2;
  animation-direction: normal;
  /* Slides right, slides right again */
}

/* Reverse: backward each iteration */
.reverse-direction {
  animation: slide 1s ease;
  animation-iteration-count: 2;
  animation-direction: reverse;
  /* Slides left, slides left again */
}

/* Alternate: forward then backward */
.alternate-direction {
  animation: slide 1s ease;
  animation-iteration-count: 2;
  animation-direction: alternate;
  /* Slides right, then slides left back to start */
}

/* Alternate-reverse: backward then forward */
.alternate-reverse-direction {
  animation: slide 1s ease;
  animation-iteration-count: 2;
  animation-direction: alternate-reverse;
  /* Slides left, then slides right back to start */
}

@keyframes slide {
  from { transform: translateX(0); }
  to { transform: translateX(100px); }
}

/* Common patterns */

/* Bouncing ball (alternate direction) */
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-100px); }
}

.bouncing-ball {
  animation: bounce 0.8s ease infinite;
  animation-direction: alternate;
  /* Up and down, up and down... */
}

/* Swinging pendulum (alternate) */
@keyframes swing {
  from { transform: rotate(-30deg); }
  to { transform: rotate(30deg); }
}

.pendulum {
  animation: swing 1.5s ease-in-out infinite;
  animation-direction: alternate;
  /* Swings left then right, back and forth */
  transform-origin: top center;
}

/* Back-and-forth scroll hint */
@keyframes scroll-hint {
  from { transform: translateY(0); }
  to { transform: translateY(10px); }
}

.scroll-indicator {
  animation: scroll-hint 1s ease-in-out 2;
  animation-direction: alternate;
  /* Down then up, twice */
}
Combining count and direction

CSS
/* Combine for complex effects */

/* Bouncy entrance: forward for emphasis */
@keyframes bouncy-entrance {
  0% { opacity: 0; transform: scale(0); }
  70% { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(1); }
}

.bounce-in {
  animation: bouncy-entrance 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  animation-iteration-count: 1;
  /* Bounces in once */
}

/* Heartbeat: infinite alternate */
@keyframes heartbeat {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.2); }
}

.heart {
  animation: heartbeat 0.6s ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  /* Constantly pulsing */
}

/* Loading dots: infinite normal */
.dot-1 { animation: fade 1s ease infinite; animation-delay: 0s; }
.dot-2 { animation: fade 1s ease infinite; animation-delay: 0.2s; }
.dot-3 { animation: fade 1s ease infinite; animation-delay: 0.4s; }

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

/* Wave effect: infinite normal with delays */
.wave-item {
  animation: wave 1s ease-in-out;
  animation-iteration-count: infinite;
  /* Continuous waving */
}

@keyframes wave {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

.wave-item:nth-child(1) { animation-delay: 0s; }
.wave-item:nth-child(2) { animation-delay: 0.1s; }
.wave-item:nth-child(3) { animation-delay: 0.2s; }

/* Flip card: one iteration, then could be repeatable */
@keyframes card-flip {
  from { transform: rotateY(0deg); }
  to { transform: rotateY(180deg); }
}

.flip-card {
  animation: card-flip 0.6s ease-in-out;
  animation-iteration-count: 1;
  /* Flips once on interaction */
}

/* Repeating flip with reverse (back and forth) */
.repeating-flip {
  animation: card-flip 0.6s ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  /* Flips back and forth forever */
}
Practical examples

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

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

/* Pulsing notification badge */
.notification-pulse {
  width: 20px;
  height: 20px;
  background: #e74c3c;
  border-radius: 50%;
  animation: pulse 2s ease-in-out;
  animation-iteration-count: infinite;
}

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

/* Animated underline for active nav */
.nav-link {
  position: relative;
}

.nav-link.active::after {
  content: '';
  position: absolute;
  bottom: -4px;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, #3498db 0%, #2ecc71 100%);
  animation: slide-underline 1.5s ease-in-out;
  animation-iteration-count: 1;
}

@keyframes slide-underline {
  from { width: 0; }
  to { width: 100%; }
}

/* Bounce animation on click */
.click-bounce {
  animation: bounce 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  animation-iteration-count: 1;
}

@keyframes bounce {
  0% { transform: scale(1); }
  25% { transform: scale(1.2); }
  50% { transform: scale(0.95); }
  75% { transform: scale(1.1); }
  100% { transform: scale(1); }
}

/* Infinite scroll prompt (then stops) */
.scroll-prompt {
  opacity: 0;
  animation: float-up 2s ease-in-out;
  animation-iteration-count: 3;
  /* Floats up 3 times then stops */
}

@keyframes float-up {
  from { opacity: 0; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-30px); }
}

/* Attention grabber: shakes back and forth */
.shake {
  animation: shake 0.5s ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

@keyframes shake {
  from { transform: translateX(-5px); }
  to { transform: translateX(5px); }
}
Performance and accessibility

CSS
/* Infinite animations can be processor-intensive */
.heavy-animation {
  animation: complex 5s ease;
  animation-iteration-count: infinite;
  /* Continuous animation = constant CPU usage */
}

/* Use will-change for optimized infinite animations */
.optimized-infinite {
  will-change: transform;
  animation: spin 2s linear infinite;
  /* GPU accelerated */
}

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

/* Alternative: allow user to pause animations */
.pausable-animation {
  animation: spin 2s linear infinite;
  animation-play-state: running;
}

.pausable-animation:hover,
.pausable-animation:focus {
  animation-play-state: paused;
  /* User can pause on hover/focus */
}

/* Avoid infinite animations that distract */
.avoid-infinite {
  animation: flash 0.2s ease;
  animation-iteration-count: infinite;
  /* Distracting flickering */
}

/* Better: use for purposeful indicators only */
.thoughtful-infinite {
  animation: gentle-pulse 3s ease-in-out;
  animation-iteration-count: infinite;
  /* Subtle, not distracting */
}
Note
`animation-iteration-count`: 1 = once, infinite = loop forever, decimals like 1.5 = partial animation. `animation-direction`: normal = forward, reverse = backward, alternate = back-and-forth. Common patterns: spinners (infinite/normal), bouncing balls (infinite/alternate), one-time entrances (1/normal). Always test infinite animations for performance and accessibility. Respect prefers-reduced-motion. Use will-change for GPU optimization.
Next
Animation fill mode: [animation-fill-mode](/css/animation-fill-mode).