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.
em
)
%
)
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.
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:
Here’s the demo HTML markup:
<span class="sprite sprite-leaf">
Text with Icon</span>
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.
See the Pen gpXZeB by Florian Stolzenhain (@Stolzenhain) on CodePen.
%
)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:
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.
See the Pen Scaling spritemaps relative to percentage size by Florian Stolzenhain (@Stolzenhain) on CodePen.
Found errors or have feedback? Send it to feedback@stolzenhain.net – thanks!
see: Dave Shea – CSS Sprites: Image Slicing’s Kiss of Death↩︎
Notably that CSS background images are not appearing in print – you need to resolve to text or fallback inline elements then.↩︎
see: Thierry Koblentz – Creating Intrinsic Ratios for Video↩︎
Note that “size” concerns either horizontal or vertical values, eg. spritemap width and sprite width or sprite vertical offset and spritemap height.↩︎
see: Sara Soreidan – A Primer To Background Positioning In CSS, 2015↩︎
see: Alex Walker – CSS: Using Percentages in Background-Image, 2007↩︎
see: Christopher Chedeau – CSS – Understanding Percentage Background Position, 2012↩︎
props for a stackoverflow answer by Koos Looijesteijn for getting me on the right track.↩︎