Web animation・Looping motion

Make repeating motion with @keyframes

transition was motion that "changes when you trigger it." With @keyframes you can make motion that loops on its own, with nothing to trigger it.

transition only moved once there was a trigger like :hover. The @keyframes we learn this time is different. It can move automatically and repeatedly, from the moment the page opens.

A badge that gently floats on its own

Even without the cursor on it, it’s moving all by itself.

How to write @keyframes

<div class="badge">NEW</div>
@keyframes float {
  0% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
  100% { transform: translateY(0); }
}
  • The float after @keyframes is the name (you can decide it however you like)
  • 0%, 50%, and 100% are the timing of the motion (0% = start, 100% = end)
  • Inside each { }, you write the look at that timing

Since 0% is the start position you can shorten it to from, and since 100% is the end position you can write it as to. When there are only two points of timing, this form is often used too.

Load it with the animation property

Just writing the script won’t make anything move. You apply it to an element with animation.

<div class="badge">NEW</div>
.badge {
  animation: float 1.6s ease-in-out infinite;
}
PartRole
floatWhich @keyframes to use (matched by name)
1.6sHow long one loop takes
ease-in-outThe rhythm of the motion (same idea as transition)
infiniteHow many times to repeat. infinite is an endless loop

If you write a number like 3 instead of infinite, it moves just that many times and then stops.

Leave the round trip to alternate

The earlier float had you write a round-trip script yourself, 0%50%100%. Actually there’s also a way to write only the way out and let the browser handle the way back. That’s alternate.

<div class="badge">NEW</div>
.badge {
  animation: float 0.8s ease-in-out infinite alternate;
}
@keyframes float {
  from { transform: translateY(0); }
  to { transform: translateY(-10px); }
}

When you add alternate, the first loop plays fromto, the second loop plays tofrom in reverse, making a back-and-forth motion. The upside is the script takes half as much writing, and the way out and the way back are always symmetric (we’ve also halved the one-loop time to 0.8s).

Offset the start with animation-delay

When you give several elements the same motion, they all tend to move in unison and look mechanical. Offsetting the start with animation-delay makes it feel much more alive.

<div class="dot"></div>
<div class="dot dot--2"></div>
<div class="dot dot--3"></div>
.dot {
  animation: float 1s ease-in-out infinite alternate;
}
.dot--2 {
  animation-delay: 0.2s;
}
.dot--3 {
  animation-delay: 0.4s;
}

That “loading” motion, where three dots bounce in a wave, is made with this time offset.

One more: a spinner that keeps rotating

<div class="spinner"></div>
.spinner {
  animation: spin 1s linear infinite;
}
@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

For an effect like rotation that “repeats the same motion forever,” it’s standard to set the easing to linear (constant speed). With ease the speed changes each loop, so it looks like it stutters at the seam.

Summary of this lesson

  1. Make the motion’s script with @keyframes name { 0% {...} 100% {...} }
  2. Apply it to an element with animation: name time easing count;
  3. infinite for an endless loop, alternate for a round trip, animation-delay for a time offset
  4. Use @keyframes for motion that repeats automatically, transition for motion that reacts

FAQ

Why doesn't my @keyframes animation move even though I wrote it?
The most common cause is that the name you gave the @keyframes and the name you wrote in the animation property aren't spelled the same. Even when the names don't match there's no error — it just doesn't move — so first compare the spelling of both.
How do I play an animation just once and then stop?
Set the count in animation to 1 instead of infinite (it's once even if you leave it out). When you want to keep the look of the final keyframe after it ends, add animation-fill-mode: forwards;.