CPCODELAB
CSS

Keyframe Animations: Timing, Fill Modes, and Iteration

11 min read

Beyond the Basics of @keyframes

You already know how to define a @keyframes rule and attach it with animation. This lesson digs into the properties that give you precise control over playback: animation-fill-mode, animation-direction, animation-iteration-count, and staggered delays.

fill-mode: controlling state before and after

animation-fill-mode determines what styles the element holds outside its active play time. both is almost always what you want: it applies the from keyframe before the delay expires and keeps the to keyframe after the animation ends.

css
@keyframes slideIn {
  from { opacity: 0; transform: translateX(-40px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* none    — snaps back after animation ends (default) */
/* forwards — keeps the final keyframe applied */
/* backwards — applies the first keyframe during delay */
/* both    — combines forwards + backwards (recommended) */
.card {
  animation: slideIn 400ms ease-out both;
}

/* Stagger children with delay */
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 80ms; }
.card:nth-child(3) { animation-delay: 160ms; }
.card:nth-child(4) { animation-delay: 240ms; }

direction and iteration-count

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

.logo {
  /* Play 3 times then stop */
  animation: bounce 600ms ease-in-out 3;
}

.loader {
  /* Alternate: plays forward then backward each cycle */
  animation: bounce 800ms ease-in-out infinite alternate;
}

/* Pause/resume with JS via animation-play-state */
.paused {
  animation-play-state: paused;
}

Multi-step keyframes and custom easing

css
@keyframes typing {
  0%   { width: 0ch; }
  100% { width: 20ch; }
}

.typewriter {
  overflow: hidden;
  white-space: nowrap;
  border-right: 2px solid currentColor;
  /* steps() produces discrete jumps — perfect for typing effect */
  animation:
    typing 2s steps(20, end) both,
    blink  0.75s step-end infinite;
}

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

Use animation-fill-mode: both by default and only override it when you need the element to snap back. The steps() timing function is the secret behind smooth discrete animations like typewriter effects and sprite-sheet frames.

Ready to test yourself?
Practice CSS— quiz & coding exercises