Scaling responsive spritemaps for CSS icons

28.08.2015 (Update)

Article Summary

Using spritemaps in responsive sites is handy if you get over the math.


This article has been made into a talk at up.front #61 – check out the slides.

Table of Contents

Why Responsive Spritemaps?

Spritemaps are a common approach in CSS1, with changing popularity over the years. They have their downsides2 but are considered pretty robust in general. Depending on your workflow, maintaing a master spritesheet helps structuring your visuals

Now, if you decided to use spritemaps in your project and apply them in reponsive context eg. on different sizes.
An obvious idea would be to manually provide different variants in the same spritesheet, a technique used for responsive logos.

Most of the time though, one simply wants to scale your original spritemap sprite without much tinkering – and it's entirely possible – in contrast to icon fonts, by the way, which can't be scaled by percentage values as they are technically text.

Another huge benefit is that, with a spritemap file in your CSS, you can easily exchange files for different file formats (eg. SVG fallbacks) or even completely different resolutions (eg. low capacity connections or retina displays) – if you scale them using on of the techniques described below.

It should be noted that SVG icons e.g <use> provide a similar solution with pretty good support by now.

Approach 1: Scaling sprites relative to font size (em)

This is the easier method and blends in with icon font techniques:
You scale your icon with the size of the surrounding text by replacing px with em values.

Needed for calculation:

  1. spritemap dimension
  2. sprite dimension and offset
  3. inherited font size for relative scaling

Here's the demo HTML markup:

<span class="sprite sprite-leaf">
        Text with Icon

The premise is simple: Dividing pixel values by your base font size and – upon scaling the text – the sprite changes size as well.

$baseFontSize: 16; // px

 * Spritemap sprites
 * 600 × 450 px

.sprite:before {
    content: "";
    display: inline-block;
    background-image: url('assets/spritemap.svg');
    background-size: (600/$baseFontSize)+em;

 * Leaf
 * 26 × 27 px
 * x: 0 px, y: 150 px

.sprite-leaf:before {
    width: (26/$baseFontSize)+em;
    height: (27/$baseFontSize)+em;
    background-position: 0 (-150/$baseFontSize)+em;

As you can see, the calculation is done in SCSS to faciliate documentation.

Annotated Demo #1: Relative font sizing

See the Pen gpXZeB by Florian Stolzenhain (@Stolzenhain) on CodePen.

Approach 2: Scaling Sprites relative to percentage size (%)

The above solution doesn't work when sprites get scaled with page elements – like in logos or link bars. So you just divide the pixel values by the element holder size and use %, right?

Right … – and wrong! Start with the following adapted code:

$baseElementSize: 960; // px

// […]

.sprite-leaf:before {
    width: (26/$baseElementSize)+%;
    padding-top: (27/$baseElementSize)+%;
    height: 0;
    background-position: 0 (-150/$baseElementSize)+%; // <-- position off

First, due to the relative height problematic in CSS, to get the relative icon height, you need to rely on the padding-trick3.
Second, this doesn't correctly place the spritemap behind the sprite box, because instead of using a regular offset, applying percentages to background-position of is calculated as some sort of gravitional center in relation to the spritemap image. Users of desktop publishing software will recognise this behaviour from alignment and distribution toolbars.

The math behind this is a tad more tricky, but still solveable:

Overcoming tricky CSS math

We begin with scaling the top left sprite we know is already in the correct place without complicated positioning. To scale the spritemap, we set it in relation to the current sprite size – the nature of this solution requires you to rescale the spritemap on every icon given that the sprite changes size4.

.sprite-spade:before {
    width: (64/$baseElementSize)+%;
    padding-top: (64/$baseElementSize)+%;
    height: 0;
    background-position: 0 0;
    /* spritemap size ÷ sprite size × 100% */
    background-size: (600/64*100%) auto;

Now for the tricky part: As noted, percentage-wise CSS background placement works pretty special567, we have to take into account not only the position on the spritemap, but also, how much the actual sprite dimension “overlaps” for correct percentaged positioning.

.sprite-spade:before {
    width: (64/$baseElementSize)+%;
    padding-top: (64/$baseElementSize)+%;
    height: 0;
    /* (sprite offset) ÷ (spritemap size - sprite size) × 100% */
    background-position: ((150)/(600-64)*100%) 0;
    /* spritemap size ÷ sprite size × 100% */
    background-size: (600/64*100%) auto;

Note that this calculation works with varying sprite sizes and offsets, unlike some other solutions8. Note also that background-position needs 2 values for x and y offset, so your code gets rather long.

Phew! Obviously, if you apply this method in larger scale, you can rely on more mixins and helpers from preprocessors like SASS to ease readability of your code – I refrained from making the demo any more complicated.

Annotated Demo #2: Percentage-based sizing

See the Pen Scaling spritemaps relative to percentage size by Florian Stolzenhain (@Stolzenhain) on CodePen.

Found errors or have feedback?
Send it to – thanks!

  1. see: Dave Shea – CSS Sprites: Image Slicing’s Kiss of Death 

  2. Notably that CSS background images are not appearing in print – you need to resolve to text or fallback inline elements then. 

  3. see: Thierry Koblentz – Creating Intrinsic Ratios for Video 

  4. Note that “size” concerns either horizontal or vertical values, eg. spritemap width and sprite width or sprite vertical offset and spritemap height. 

  5. see: Sara Soreidan – A Primer To Background Positioning In CSS, 2015 

  6. see: Alex Walker – CSS: Using Percentages in Background-Image, 2007 

  7. see: Christopher Chedeau – CSS – Understanding Percentage Background Position, 2012 

  8. props for a stackoverflow answer by Koos Looijesteijn for getting me on the right track.