# Animate a sprite strip with CSS steps()

Start with one horizontal PNG containing eight equally sized frames. Give a CSS element the dimensions of one displayed frame, show the strip as its background, and move that background one frame at a time with `steps(8, end)`. This example produces an eight-frame loop using HTML and CSS alone. Its original CC0 input is 128 × 16 pixels; the preview displays 64 × 64 pixels per frame and loops every 800 ms.

## Run the sample

For the downloaded project, extract the ZIP and open `index.html` in a browser. Keep `style.css` and the `assets` folder next to it. No package install, remote script, account or image upload is needed. The first preview animates correctly; the second intentionally demonstrates the wrong number of steps. The checkbox pauses both previews.

From the QuickUpscale repository, generate the original PNG and runnable download first:

```sh
node examples/tutorials/css-sprite-animation/generate.mjs
node --test examples/tutorials/css-sprite-animation/fixture.test.mjs
node examples/tutorials/css-sprite-animation/generate.mjs --check
```

Open `examples/tutorials/css-sprite-animation/index.html`, or serve that directory with a local static server. The generator writes ignored `assets/` and `dist/css-sprite-animation.zip` caches from tracked source. Its PNG encoder and stored ZIP writer are the repository's existing helpers, with no additional dependency. The ZIP contains the HTML, CSS, original PNG, geometry/hash manifest, this README and the CC0 license. Generation does not rewrite equal content.

## 1. Declare the strip geometry

The source artwork contains eight 16 × 16 cells in one row, without gutters or outside margins. For display at 4×, the viewport is `16 × 4 = 64` pixels, and the background is `8 × 64 = 512` pixels wide. Inspect the complete strip below the animated previews; each frame's amber marker moves by one source pixel.

```css
.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; }
}
```

Keep the displayed cell size, background size and final offset in the same coordinate system. `image-rendering: pixelated` controls the displayed pixel edges; it does not add missing frame metadata or repair a blurred input. Use explicit pixel offsets here: percentage background positions use the difference between the element and image sizes, making them easy to misinterpret.

## 2. Use the full strip width as the terminal offset

With an infinite animation and `steps(8, end)`, the visible positions are 0, −64, −128, −192, −256, −320, −384 and −448 pixels. The declared −512 pixel endpoint is one full strip width. At the next iteration, the animation returns to 0 before a blank ninth frame is displayed.

Do not combine `steps(8)` with a terminal offset of −448 pixels. That divides the movement into 56 pixel jumps, which do not match the 64 pixel cells. For this uniform eight-frame loop, the full-width terminal offset and eight end steps belong together.

## 3. Set frame duration and provide a stop

Eight frames over 800 ms gives each frame 100 ms, or 10 frames per second. Change the total duration to change speed; keep the number of steps equal to the number of cells. This CSS method gives every frame equal duration. Unequal frame delays require a different keyframe or JavaScript playback scheme.

The sample's labeled pause checkbox sets `animation-play-state: paused`; unchecking it resumes at the paused position. Also honor the reader's reduced-motion preference:

```css
@media (prefers-reduced-motion: reduce) {
  .sprite {
    animation: none;
    background-position: 0 0;
  }
}
```

Enable the browser developer tools' `prefers-reduced-motion: reduce` emulation, or the operating system's equivalent preference. The sample then shows the first frame and a text explanation. The pause checkbox does not override that preference.

## 4. Inspect an important failure

The right-hand preview uses `steps(7, end)` while retaining the eight-cell strip and −512 pixel endpoint. Its jumps are approximately 73.14 pixels. At 400 ms it lands near −219.43 pixels, between the expected −192 and −256 positions, so the viewport slices across adjacent frames. Correct the step count to eight; adding smooth interpolation cannot fix incorrect geometry.

## Inspected result and limits

Executed in ego-browser Chromium 154.0.8037.44 on macOS at device-pixel ratio 1. The dated T34 CSS proof document records the raw evidence and limits. The proof checks the first and final frame, the 799 → 800 ms loop boundary, the wrong-step case, pause/resume and reduced motion in an actual browser. Exact timing inspection seeks the browser's CSS animation timeline; it is not a claim that every device renders a fresh image at precisely 100 ms intervals. Browser scheduling, background-tab throttling and display refresh can affect visible playback. In this browser, seeking exactly to 300, 600 and 700 ms returned the preceding frame; seeking 0.001 ms later returned the expected next frame. The 799 → 800 ms loop boundary was correct. Treat the 100 ms duration as the animation schedule, not a frame-accurate media clock.

This sample accepts no uploaded files and infers no timing from a PNG. Multi-row sheets, gutters, packed/rotated atlas cells and per-frame durations need different coordinates or playback data. Use a sheet editor or splitter to make a gap-free horizontal strip first, then update all four geometry values together. Keep decorative animation out of the accessibility tree in a real page; this instructional sample gives the preview images explicit labels.

## Sources and reuse

- [MDN: animation-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/animation-timing-function) explains stepped timing and step positions.
- [W3C CSS Easing Functions: step easing functions](https://www.w3.org/TR/css-easing-1/#step-easing-functions) defines the step function.
- [MDN: prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) documents the user preference.
- [MDN: background-position](https://developer.mozilla.org/en-US/docs/Web/CSS/background-position) explains pixel and percentage positioning.

The artwork is reflowed from the original QuickUpscale 4 × 2 proof sheet; no third-party artwork is included. Reuse the sample and artwork under CC0; see `LICENSE.txt`. No claim about search traffic or an editor's inability to perform this task follows from these sources.
