With new features in CSS 3 such as animation and three-dimensional transforms, you can do some pretty amazing things. With multiple animations on one element, it’s possible to take a sprite sheet and animate it frame by frame. This tutorial assumes basic knowledge of CSS3. Knowing CSS3 animations is a plus.

At the time of this tutorial, only Chrome, Safari, and possibly Internet Explorer are supported. This is due to inconsistent support of background-position-x and background-position-y among different rendering engines. If you find that your animations do not work, try using one of these browsers. Update 16 Jan 2018: Almost all the major browsers now support background-position-x/y consistently, meaning that this tutorial should work in Chrome, Firefox, Safari, IE, Microsoft Edge, and Opera. You may check https://caniuse.com/#search=background-position for the latest cross-browser compatibility.

What’s a sprite sheet?

A sprite sheet is a single image that consists of a bunch of smaller images. The easiest way to explain it is to look at a picture of one:

Sprite sheet "Running Grant"
Source: http://smwiki2014.wikidot.com/wiki:android-game-development

Reading from left to right, top to bottom, notice how our figure changes position ever so slightly. In fact, if we cut this large image up into its constituent images and rapidly flash them in sequence, we will get the illusion of movement.

Primer: one-dimensional sprite sheet animation

Before delving into two dimensions, let’s take a look at how we can animate just the top row of the sprite sheet above.

First we must know how to show one part of the image. The easiest way to achieve this is with a div of set width and height, and a background image. There are other ways of achieving this, but we will stick with this method.

<div id="sprite"></div>
#sprite {
    height: 294px;
    width: 165px;
    background-image: url(http://www.fabiobiondi.com/blog/wp-content/uploads/2012/08/runningGrant.png);
    background-position: 0 0;
}

Setting the width and height on the div effectively crops the background image. The specific numbers are derived from the dimensions of each frame of the sprite sheet. The important property here is background-position, which we will use to shift the background image inside the div. Right now it is set to 0 0, which shows the background image starting at the top-left corner of the image.

Defining the animation

What we want to do then, is rapidly change the value of background-position in our animation. First, let’s define that animation. Add this to the bottom of your CSS.

@keyframes run-x {
    from {
        background-position-x: 0;
    }
    to {
        background-position-x: -1980px;
    }
}

In the code above, we start at 0 for our background-x-position. By the end of the animation, the background will have shifted to -1980px. How did we arrive at this number? Let’s temporarily ignore the negative sign. Examining the image of running Grant, we see that each “frame” is 165 pixels wide. The sprite sheet is 12 frames across. By simple math, we figure out that the last frame is positioned at 12 Ɨ 165px = 1980px.

Now you may wonder why there is a negative sign. Background position is a strange beast; it is not specified in terms of the image itself, but rather the element that has the background image. Do not worry too much about whether to use a negative sign, as this tutorial will give you the exact numbers to use.

Apply the animation

Right now nothing on your screen will move. That’s because we haven’t referred to the animation yet. Now we’ll modify the CSS for #sprite. The changes are highlighted.

#sprite {
    width: 300px;
    width: 165px;
    background-image: url(http://www.fabiobiondi.com/blog/wp-content/uploads/2012/08/runningGrant.png);
    background-position: 0 0;
    animation-name: run-x;
    animation-duration: 0.4s;
    animation-iteration-count: infinite;
    animation-timing-function: steps(12);
}

Note: when using Chrome or Safari, prefix all animation-* properties with -webkit-. For example, animation-name becomes -webkit-animation-name.

The four lines of code we added define the animation that is applied to the element and some properties of the animation.

  • animation-name is name of the animation is what we just defined: “run-x”.
  • animation-duration says that this animation lasts for 0.4 seconds
  • animation-iteration-count sets the animation to loop ad infinitum
  • The animation-timing-function, steps(12) tells us to break up the animation into twelve discrete stages. Try removing this line of code. You’ll notice a sort of filmstrip effect, where it feels like someone is pulling the sprite sheet at a rapid speed. This is because the animation is hitting every value between 0 and -1980 pixels. The steps function makes it so that the background is at 0 for some time, then -165px, then -330px, and so on. Rather than a continuous progression of values, steps() makes the CSS properties take on discrete values per unit of time. And this is really what sprite animation is all about: taking a frame and displaying it for a short time, showing the next frame and displaying that frame for a short time, and so on.

Intermediate step: another animation

If we want the animation to run in two dimensions, then we have to change the y-position of the background along with the x-position. The concept is the same as before; only this time we will use background-position-y instead of background-position-x. At the bottom of your CSS, add a new keyframe definition.

@keyframes run-y {
    from {
        background-position-y: 0;
    }
    to {
        background-position-y: -1764px;
    }
}

Each frame’s height is about 294 pixels, and the sheet is 6 frames tall. 294 \times 6 = 1764px. Note that we don’t have to touch background-position-x. We already have an animation definition for manipulating this property.

We will not apply this animation immediately. Instead, we’ll combine it with the run-x animation we defined above to produce the 2D animation.

Putting it all together

One of the beautiful things about CSS3 animations is that it can accept multiple animations per element. We have already applied the animation in the x-direction. Now, we just add on the y-direction animation.

#sprite {
    width: 300px;
    width: 165px;
    background-image: url(http://www.fabiobiondi.com/blog/wp-content/uploads/2012/08/runningGrant.png);
    background-position: 0 0;
    animation-name: run-x, run-y;
    animation-duration: 0.4s, 2.4s;
    animation-iteration-count: infinite;
    animation-timing-function: steps(12), steps(6);
}

And there you have it! Here’s an explanation of the changes made in order to produce the effect:

  • I added run-y to animation-name to apply another animation at the same time as the existing run-x animation.
  • Skipping to the timing function, run-y is presented in six discrete steps, as the sprite sheet is 6 frames tall. This will become important in determining the duration of run-y
  • run-x runs in 0.4 seconds, and run-y runs in 2.4 seconds. How did we arrive at these numbers? The idea is that we want to run the first row completely, then immediately jump to the next row and run its length, and so on. It takes 0.4 seconds to run an entire row of the sprite sheet. Think of the run-y animation as “selecting” a row. We want to select a row for 0.4 seconds, then select the next row for 0.4 seconds, and so on. There are six rows altogether in the sprite sheet, so run-y should take 0.4 Ɨ 6 = 2.4 seconds to run.
  • Both animations run on loop, so animation-iteration-count is not changed.

A short word about shorthand

Instead of writing

animation-name: run-x, run-y;
animation-duration: 0.4s, 2.4s;
animation-iteration-count: infinite;
animation-timing-function: steps(12), steps(6);

we can write

animation: run-x 0.4s infinite steps(12),
    run-y 2.4s infinite steps(6);

This is called shorthand, where multiple CSS properties are compressed into one. The animations themselves are separated by commas, while animation properties are separated by spaces.

The whole thing

You can see a complete demonstration, along with source code, at http://codepen.io/g-liu/pen/XbrMzr?editors=110. At the time of this post, the demo only works on Chrome and Safari, due to lack of support for background-position-x and background-position-y.

Published by Geoffrey Liu

A software engineer by trade and a classical musician at heart. Currently a software engineer at Groupon getting into iOS mobile development. Recently graduated from the University of Washington, with a degree in Computer Science and a minor in Music. Web development has been my passion for many years. I am also greatly interested in UI/UX design, teaching, cooking, biking, and collecting posters.

12 thoughts on “Animating a 2D sprite with CSS animation

  1. “Good point. This is actually caused by the sprite sheet itself. In the last row there are some blank frames and they show up in the animation.”

    And how to fix this error? Is there a code for this?

    • The sprite sheet is just an example, this CSS animation technique will work with any sprite sheet. You’ll need to find one that has no blank frames. Or, you can take the current sprite sheet and reformat it so there are no blank frames. I don’t believe CSS itself is powerful enough to know to “skip the last N frames”.

  2. Hi, great tutorial. Will this be responsive?. Can you please explain me with code, if this won’t be an responsive css sprite animation.

    • If you want the animation to be as large as the user’s screen, you should play around with the background-size property.

      If each frame of your animation is very large, and you want it to fit on small screens, you can also take a look at the max-width and max-height properties.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.