Home / Articles / Animation & Motion /

animation

Animation iteration count and infinite loops

Control how many times a keyframe animation repeats. When to use infinite vs finite iterations, and how to stop looping animations cleanly.

Staggered animations with iteration

For list animations, combine iteration-count with staggered delays to create cascading entrance effects. Each item in a list can animate with the same duration but different delays, creating a wave-like entrance that feels polished and intentional.

The key to effective staggered animations is keeping iteration counts consistent across all items while varying delays. If item 1 has infinite with 1.4s delay, item 2 should also have infinite but with a 1.0s delay. This ensures all items stay in sync rhythmically while appearing offset in phase.

Modern CSS now supports animation-timeline with view() and scroller() functions, enabling scroll-triggered iteration counts. This means elements can start their animation cycle when they come into view, with automatic iteration control based on scroll position. While still experimental, this represents the future of scroll-linked animation iteration.

For immediate cross-browser compatibility, JavaScript IntersectionObserver remains the most reliable method for controlling when animations start and how many times they repeat based on visibility. This pattern is especially powerful when combined with dynamic iteration counts that change based on scroll speed or direction.

When designing staggered infinite animations, be mindful of the visual density. A list of 20+ items all pulsing or bouncing with offset timing can create a strobe-like effect that overwhelms the user. Consider reducing iteration counts for items further down long lists, or using CSS to pause animations once they've scrolled out of the active viewport area.

Debugging iteration issues

Debugging animation iteration problems often comes down to unexpected timing or state conflicts. When an animation doesn't repeat as expected, the browser DevTools timeline can reveal exactly what's happening.

In Chrome DevTools, the Animation panel shows each iteration as a separate block on a timeline. This visual representation makes it easy to spot issues: does the animation stop a cycle early? Does it pause unexpectedly? You can also toggle iteration counts live to test different values without refreshing.

A common pitfall is combining animation-direction: alternate with fractional iteration counts. Since alternate makes each "iteration" include both forward and reverse, a count of 1.5 doesn't mean "one-and-a-half cycles through the forward animation" — it means one full forward-backward cycle, plus half of another forward-backward cycle, which could end mid-reverse.

Another frequent issue is the interaction between animation-delay and animation-iteration-count. Negative delays can make animations appear to start mid-cycle, which combined with fractional iteration counts creates confusing stopping points. Always visualize your expected behavior by drawing out the timeline on paper first.

Stopping infinite animations

Once an animation is infinite, stopping it requires JavaScript or a state change. The common pattern is to use a class to control the loop state.

A more flexible approach is to use CSS custom properties to control the iteration count dynamically. This lets you change the loop behavior without adding or removing classes. You can set an initial value of infinite and override it with a specific number when the operation completes.

For accessibility, always provide a way for users to stop infinite animations manually. Some users may find continuous motion disorienting, even with prefers-reduced-motion overrides in place.

Another pattern is to use the animation-play-state property. Instead of removing the animation entirely, pause it. This preserves the animated state so it can resume if needed, and it's often simpler than managing class transitions.

The animation-play-state approach is particularly useful for interactive elements that pause on hover. A loading spinner that stops when users move their cursor over it provides visual feedback that the element is interactive without losing the loading context.

Performance considerations

Infinite animations never stop, so they consume CPU/GPU resources continuously. For best performance:

  • Keep infinite animations running at compositor-only properties: transform and opacity.
  • Use long durations (> 2s) for subtle continuous animations to reduce frame processing.
  • Pause or remove infinite animations when elements are off-screen.
  • Combine will-change with infinite for predictable first-frame rendering.

Modern browsers are quite efficient with infinite animations, but problems arise when multiple infinite animations run simultaneously across the page. A dashboard with 10+ infinite indicators can drain battery and cause frame drops even on desktop hardware.

The Intersection Observer API can help by pausing animations for elements scrolled out of view. This is especially important for infinite grids or lists where many animated items exist but only a handful are visible at once.

Practical examples

Common patterns combining iteration count with other properties:

/* Attention-seeker: 3 pulses then stop */
.new-item {
  animation: attention-pulse 0.6s ease-out;
  animation-iteration-count: 3;
  animation-fill-mode: forwards;
  animation-direction: alternate;
}

@keyframes attention-pulse {
  0%, 100% {
    background: oklch(0.92 0.02 250);
    transform: scale(1);
  }
  50% {
    background: oklch(0.85 0.04 250);
    transform: scale(1.08);
  }
}

/* Scanning line effect: infinite bounce */
.scanner-line {
  width: 100%;
  height: 2px;
  background: linear-gradient(90deg, 
    oklch(0.55 0.18 250 / 0) 0%,
    oklch(0.55 0.18 250) 50%,
    oklch(0.55 0.18 250 / 0) 100%
  );
  animation: scan 2.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
  animation-direction: alternate;
}

@keyframes scan {
  from { transform: translateY(0); }
  to { transform: translateY(200px); }
}

/* Loading dots: 3 sequential animations with oklch colors */
.loading-dots {
  display: flex;
  gap: 0.5rem;
}
.dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: oklch(0.55 0.18 250);
  animation: blink 1.4s ease-in-out infinite;
}
.dot:nth-child(1) { animation-delay: -0.8s; }
.dot:nth-child(2) { animation-delay: -0.4s; }
.dot:nth-child(3) { animation-delay: 0s; }

@keyframes blink {
  0%, 80%, 100% { 
    opacity: 0.4;
    transform: scale(0.8);
  }
  40% { 
    opacity: 1;
    transform: scale(1);
  }
}

/* Progress ring with controlled iteration */
.progress-ring {
  width: 64px;
  height: 64px;
  border: 4px solid oklch(0.9 0.02 250);
  border-top-color: oklch(0.55 0.18 250);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

/* Success checkmark: finite, no loop */
.success-check {
  animation: pop-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}

@keyframes pop-in {
  from {
    opacity: 0;
    transform: scale(0.3) rotate(-20deg);
  }
  to {
    opacity: 1;
    transform: scale(1) rotate(0);
  }
}

These patterns are widely reusable and work well with prefers-reduced-motion overrides that replace infinite loops with static styles. Notice how the oklch() values maintain perceptual consistency across color transitions in the animated states.

Fractional iteration counts in practice

Fractional iteration counts like 2.5 are rarely discussed but incredibly useful for creating intentional partial animations. When you specify animation-iteration-count: 2.5, the animation completes two full cycles through all keyframes, then runs a third cycle only halfway before stopping. For a 4-keyframe animation spanning 0%, 33%, 66%, and 100%, the half-iteration stops at the midpoint of the final cycle (around 50% of the timeline).

/* Halfway through final cycle */
 .partial-pulse {
   animation: pulse 1.2s ease-in-out 2.5;
   animation-fill-mode: forwards;
 }

 @keyframes pulse {
   0%, 100% {
     background: oklch(0.85 0.03 250);
     transform: scale(1);
   }
   50% {
     background: oklch(0.65 0.15 250);
     transform: scale(1.15);
   }
 }

 /* Demonstrating fractional stopping point */
 .fraction-demo {
   width: 8px;
   height: 8px;
   border-radius: 50%;
   background: oklch(0.65 0.15 250);
   animation: pulse 1.5s ease-in-out 3.5;
   animation-fill-mode: forwards;
   margin: 1rem 0;
 }

Use fractional counts when you want an animation to "settle" at a midpoint state rather than completing a full cycle. A 1.5 iteration count is perfect for hover effects that need to emphasize a change but return smoothly—run the animation once, then halfway through the return cycle. The fill-mode: forwards property becomes critical here, ensuring the element stays at whatever keyframe position the fractional iteration lands on.

Summary

animation-iteration-count is essential for controlling animation repetition. Choose your iteration count based on the animation's purpose and desired user experience.

Finite iteration counts (1, 3, 5) work best for entrance animations, micro-interactions, and attention-drawing effects that should settle into a final state. Always pair finite counts with fill-mode: forwards to maintain the final animated state after completion.

Infinite loops should serve specific functional purposes: loading indicators, active status markers, or subtle scanning effects. Use longer durations (2s+) for background effects and shorter durations (<1s) for urgent indicators. Always respect prefers-reduced-motion by either pausing or eliminating infinite animations for users who need it.

Fractional iteration counts (1.5, 2.25) create intentional, incomplete-looking animations that feel deliberate rather than broken. They're perfect for micro-interactions and one-off emphasis effects.

Modern color systems like oklch() ensure smooth, perceptually uniform color transitions in your animations, especially important for infinite loops that repeat the same color cycle endlessly.

Key takeaways:

  • Default is 1 — animation runs once then stops.
  • Use infinite for loading indicators, but pair with reduced-motion support and keep animations on compositor-only properties.
  • Use fractional values for partial cycles that feel intentional.
  • Combine with fill-mode: forwards to maintain final state for finite animations.
  • Prefer compositor-only properties (transform, opacity) for infinite animations.
  • Use modern color functions like oklch() for smooth color transitions.
  • Consider performance: fewer simultaneous infinite animations equals better battery life and smoother frames.
  • Always provide manual controls or pauses for extended infinite animations.