Eight frames, one CSS animation

This original 128 × 16 PNG contains eight 16 × 16 frames. The preview displays each frame at 4× size, for 100 ms per frame.

Correct: steps(8, end)
0 → −512 px in 800 ms
Failure: steps(7, end)
73.14 px jumps slice across cells

Reduced motion is active: the preview displays the first frame without animation.

The strip

Read left to right. The amber dot moves to a different column in each frame.

All eight green character frames, with amber position markers

Use the geometry together

.sprite {
  width: 64px;
  height: 64px;
  background: url("assets/sprite-strip.png") 0 0 / 512px 64px no-repeat;
  image-rendering: pixelated;
  animation: strip 800ms steps(8, end) infinite;
}
@keyframes strip {
  to { background-position-x: -512px; }
}
@media (prefers-reduced-motion: reduce) {
  .sprite { animation: none; background-position: 0 0; }
}

The terminal −512 px is one full strip width. Just before the loop, the visible final frame is −448 px. At 800 ms, the next iteration returns to 0 px. See README.md for the full procedure, inspected results and limits.