Responsive CSS sprites

If you’re reading this, you probably already know what is a sprite. In web development, this technique allows to limit the number of requests to the server by downloading a single image composed by a number of smaller images. To be clear in this article, the sprite will refer to the larger image containing the combination of the smaller images, that will be called tiles.

Then, to show your tile, you just need to create an element (e.g. <i class="image-1">) and use the coordinates of the tile – using the sprite as reference system – as background-position, and the original dimension of the tile as width and height for that class.

This approach works and is widely used today, but the problem is that it’s not responsive. Sprites (and so tiles) generated this way don’t scale on smaller resolutions, nor you can’t simply add a max-width: 100%; as you would do to any <img> element.

In a hurry?

Jump to the demo

Could background-size help?

Background-size is a CSS property that lets you – as you may imagine – set the size of the background image. But everyting will be clearer with an example.

Consider the following image composed by three sprites with the following dimensions:

  • 100×200
  • 150×100
  • 300×300

Sprite

The resulting image will have the maximum width among tiles as width, and the sum of all tiles’ heights as height. In this example, 300×600.

A percent value for this background-size refers to the sprite width. Each tile’s background size in percent has to be calculated with the following formula:

100 * spriteWidth / tilewidth

So, for the three tiles we’ll have: 300%, 200%, 100%. In other words, background-size could be read as a zoom value: to fill a 300px wide space, a tile wide 150px must have a background-size value of 200%.

How does background-position work?

This is quite difficult to grasp at the beginning. Official MDN documentation: “percentages values refer to the size of the background positioning area minus size of background image; size refers to the width for horizontal offsets and to the height for vertical offsets”. What does this mean?

Background-position has two values: x and y. We fix the x to 0 for simplicity. This is why the generated sprite has only one sprite per row, and every tile is aligned to the same value (0) in the x axis.

To calculate the second value, this is the formula:

100 * tileYOffset / abs(tileHeight - spriteHeight)

This is pretty counter-intuitive to me. If you want to deepen your knowledge, this is the best article I found about background-position explanation.

For our example tiles, we have:

  • 100 * 0 / abs(200 – 600) = 0%
  • 100 * 200 / abs(100 – 600) = 40%
  • 100 * 300 / abs(300 – 600) = 100%

This is the basic we need to correctly position and size our tiles. Now let’s make them fully responsive.

Additional CSS

We need to force the tile to have a constant aspect ratio. The width of the tile can be set to the same percent value we calculated for background-size. But we can’t use the height rule for it: its value in percent refers to the height of the container (which is not known). The best way I found to solve this is to set the height to 0, and use padding-bottom. Padding in percent refers to the width of the container, which we explicitly set.

To calculate padding bottom, we just calculate the width / height ratio from the original tile’s width in pixels:

tileHeight / tileWidth * 100

For our three tiles:

  • 200 / 100 * 100 = 200%
  • 100 / 150 * 100 = 66.666666%
  • 300 / 300 * 100 = 100%

This is enough to guarantee that the correct aspect ratio is kept. Now we have fully responsive tiles that resize maintaining the aspect ratio at browser resize. Naturally they all have a max-width: 100% to guarantee responsiveness. The problem is that they fill all the container’s width without stopping to grow, while it’d be nice that their size could be at most the original tile’s one.

This problem could be solved using pseudo-elements, in particular I used ::after. The goal is to have the minimum markup possible and rely mostly on the (generated) css to show the tile from our sprite. By setting some empty content and all the properties we calculated until now to a ::after element for every tile class, we now only need to set a max-width and a max-height to their container (the tile element itself). I guess it’ll be clearer with a full example. Let’s take our second tile:

Markup:

<i class="icon-150x100"></i>

CSS:

.icon-150x100 {
    max-width: 150px;
    max-height: 100px;
}

.icon-150x100::after {
    /* the following rules are inherited by the generic common tile style */
    content: ' ';
    display: inline-block;
    max-width: 100%;
    background-size: 100%;
    background-image: url('sprite.png');

    /* class specific rules */
    background-position: 0 40%;
    background-size: 200% auto;
    height: 0;
    padding-bottom: 66.6667%;
    width: 200%;
}

Here you can find a fully functional proof of concept – a live demo accepting only PNG and a limited number of tiles at the moment.

This approach has been tested on the latest versions of Chrome and Firefox, but it should be fully supported by IE9+ too. Probably also by IE8 just by changing the pseudo elements from ::after to :after.
The next step will be to modify compass or some sprite generator software (such as spritesmith) to generate this responsive sprite code.

This work has been inspired by http://responsive-css.spritegen.com, thanks to his author Greg.

Also read...

Leave a Reply

Your email address will not be published. Required fields are marked *