Keyframe Animations

An animation plays an @keyframes sequence (@keyframes) on its own clock: animation: slidein .5s ease-out 1s 3 alternate both sets name, duration, timing function (applied between each pair of keyframes, overridable inside one), delay, iteration count (or infinite), direction and fill mode; animation-play-state pauses it. Fill mode backwards applies the first keyframe during the delay, forwards keeps the last one afterwards, and both does both. A negative delay starts part-way through, which staggers elements or, with paused, freezes a frame.

A squash-and-stretch bounce, one frame every 125 msHTMLLive
<style>
  body { margin: 10px; display: flex; gap: 8px; font: 12px system-ui, sans-serif; }
  figure { margin: 0; width: 72px; height: 130px; position: relative; text-align: center;
           border-bottom: 3px solid #37474f; background: #eceff1; }
  .ball { position: absolute; left: 20px; bottom: 0; width: 32px; height: 32px;
          border-radius: 50%; background: #ef6c00; transform-origin: 50% 100%;
          animation: bounce 1s infinite paused; animation-delay: calc(var(--f) * -125ms); }
  @keyframes bounce {
    0%, 100% { translate: 0 0;     scale: 1.25 .75; }
    15%      { translate: 0 0;     scale: .9 1.1;  animation-timing-function: ease-out; }
    50%      { translate: 0 -75px; scale: 1 1;    animation-timing-function: ease-in; }
    90%      { scale: .9 1.1; }
  }
</style>
<script>
  for (let f = 0; f < 8; f++)
    document.write(`<figure>${f * 125} ms<i class="ball" style="--f:${f}"></i></figure>`);
</script>
Browser output of Listing 4.62
Browser output of 62

Per-keyframe timing functions slow the ball as it rises and speed it up as it falls; animating only translate and scale keeps it on the compositor (Animation Performance). Script gets animationstart, animationiteration and animationend events, and el.getAnimations() returns controllable Animation objects. Wrap large movement in @media (prefers-reduced-motion: no-preference).