CSS steps() for Frame-by-Frame Sprite Animations
This article explains how the CSS property
animation-timing-function: steps(4, jump-end) enables
retro, stop-motion, and frame-by-frame sprite sheet animations. By
replacing continuous transitions with abrupt, calculated shifts, the
steps() timing function prevents the browser from smoothly
interpolating movement. You will learn the mechanics behind sprite-based
positioning, how the parameter values break down, and how to implement a
fully functioning four-frame animation.
The Problem with Continuous Timing Functions
In standard CSS animations, timing functions like ease,
linear, or cubic-bezier() calculate
intermediate visual states between keyframes. If you animate a sprite
sheet using linear, the browser smoothly slides the image
across the view area. This produces an unwanted scrolling or smearing
effect rather than the illusion of distinct, traditional animation
frames.
Sprite animations require instantaneous cuts from one frame to the next.
Deconstructing
steps(4, jump-end)
The steps() function turns an animation into a staircase
progression instead of a smooth curve. It takes two primary parameters:
the number of steps and the step position behavior.
1. The Step Count: 4
The integer 4 defines the number of equal intervals
dividing the total animation duration. If an animation runs for 1
second, a step count of 4 splits the timeline into four
distinct 0.25-second intervals. The element will display exactly four
distinct positions corresponding to the four sprite frames.
2. The Direction Term:
jump-end
The second parameter controls precisely when the jump between values occurs within each interval.
The jump-end keyword specifies that the visual change
happens at the end of each time interval:
- Step 1 (0.00s – 0.25s): Displays the initial state (Frame 1). At 0.25s, it abruptly jumps to Frame 2.
- Step 2 (0.25s – 0.50s): Displays Frame 2. At 0.50s, it abruptly jumps to Frame 3.
- Step 3 (0.50s – 0.75s): Displays Frame 3. At 0.75s, it abruptly jumps to Frame 4.
- Step 4 (0.75s – 1.00s): Displays Frame 4. At 1.00s, the cycle finishes and loops back to Frame 1.
jump-end ensures that the starting frame is visible
right away at 0% and that each frame remains static on screen for the
full duration of its segment. (Note: jump-end is the modern
standard syntax for the legacy end keyword).
Implementation Example
To create the animation, place a sprite sheet containing four equal-width frames inside an element sized to display only one frame at a time, hiding any overflow.
HTML
<div class="character"></div>CSS
.character {
width: 100px;
height: 100px;
background-image: url('spritesheet-400x100.png');
background-repeat: no-repeat;
background-position: 0 0;
animation: play-sprite 0.8s steps(4, jump-end) infinite;
}
@keyframes play-sprite {
from {
background-position: 0 0;
}
to {
background-position: -400px 0;
}
}How the Mechanics Work Together
- Sprite Sizing: The container element is set to
100pxwide, matching the width of a single frame. The complete horizontal sprite image is400pxwide (4 frames × 100px). - The Keyframes: The
@keyframesrule shifts thebackground-positionfrom0to-400px, which is the total width of the image. - The Step Division:
steps(4, jump-end)divides the-400pxmovement into four increments of-100px. - Execution: The browser renders the background at
0px, then instantly snaps to-100px,-200px, and-300pxat each quarter-interval of the animation duration. Because the movement is instantaneous, the user perceives a seamless, frame-by-frame animation without motion blur.