Keyframe
Keyframe · The log

Infinite Animation in CSS: Loops That Do Not Get Annoying

July 24, 2026 · Uncategorized

An infinite animation runs until the page closes. That is a lot of trust to place in a few lines of CSS, because a loop that felt charming on the second viewing can feel like a dripping tap by the hundredth. Endless motion is worth doing, but only when the loop closes cleanly and the movement is quiet enough to live with.

The one keyword that makes it loop

Everything hinges on animation-iteration-count: infinite, usually written into the shorthand as just infinite. It tells the browser to restart the keyframes the instant they finish, forever.

.float {
  animation: float 4s ease-in-out infinite;
}

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

This gentle bob is the workhorse of hero illustrations and floating badges. The reason it loops cleanly is buried in the keyframes: the 0 percent and 100 percent states are identical, both at translateY(0). When the animation ends where it began, the restart is invisible.

The seam problem

Most ugly loops fail at exactly one point: the moment they restart. If the last frame does not match the first, the element jumps, and your eye catches that jump every single cycle. It is the animation equivalent of a song that does not loop cleanly.

There are two ways out. The first, shown above, is to make the start and end frames identical so the loop closes on itself. The second is to run the animation in alternate direction, which plays the keyframes forward, then backward, so it always returns smoothly to the start.

.sway {
  animation: sway 3s ease-in-out infinite alternate;
}

@keyframes sway {
  from { transform: rotate(-3deg); }
  to   { transform: rotate(3deg); }
}

With alternate you only write half the motion, from one extreme to the other, and the reverse pass gives you the return for free. It is less code and the seam disappears by definition, since the end of a backward pass is the start of the next forward one.

A pulse that pulls the eye

A slow pulse on a notification dot or a call-to-action draws attention without shouting. Scale and opacity together read as a soft heartbeat.

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

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

Holding the final frame from 70 to 100 percent builds in a pause before the next pulse, so the effect breathes instead of hammering. A loop with no rest in it becomes noise fast.

When not to loop forever

An endless animation is a small, permanent claim on attention, so spend it carefully. One quiet floating element on a page is a nice touch. Five things all looping at once is a slot machine, and the reader cannot settle on the content because something is always moving in the corner of their eye.

The rule of thumb: loop things that are meant to be ambient, and let purposeful motion play once and stop. A spinner loops because the wait is ongoing. A button press should fire and settle, which is the logic behind button animation. And for anyone who would rather have none of it, cut every loop under reduced motion, per accessible animation. The pillar on CSS animation holds the full map.

← Back to the log