Last week I attempted to use the CSS sprites feature of Compass for the second or third time. It's been a struggle each time, but the power and potential is there, so I keep coming back. This time was a bit different, though, because I finally decided to stop relying on the docs and dive into the code.
Before I go into the nitty-gritty, let's take a step back and talk about why I want so badly to use this feature and why it's been painful each time. The why is easy: CSS sprites significantly reduce HTTP requests, and manually building sprite maps and calculating sprite positions is a pain in the ass. If I'm going to use sprites, it has to be done in an automated fashion. Since I'm already using Compass, and Compass has CSS spriting built-in, it seems a no-brainer. This is a feature I need to be using.
Unfortunately, the docs leave much to be desired. You might read those docs and be convinced that the "right" way to use CSS Sprites is with magically-generated CSS classes applied to your markup. Not so. There is a better way to use sprites that is more straightforward, easy to understand, and without future developers having to understand magically generated mixins and classes.
To get there, let's dive into the code that makes the "magic" classes work.
This
template
is where all of those magic mixins are defined when you import your sprites
with something like @import "my-icons/*.png"
. You can see that these
generated mixins are straighforward and in most cases are just delegating to a
regular mixin defined
here.
While not documented, there is no reason we can't use those mixins ourselves.
Let's say you want to apply a background image called "ribbonfull.png" to your
h3
tags. Following the docs, your code might look like this:
@import "spritemap/*.png"
+all-spritemap-sprites
<h3 class="spritemap-ribbonfull">Test</h3>
Let's turn that into something more maintainable and customizable. Under the covers, the "spritemap-ribbonfull" class is really just applying a couple mixins. We can do the exact same thing with this:
@import "spritemap/*.png"
h3
background: $spritemap-sprites no-repeat
+sprite($spritemap-sprites, ribbonfull)
The only bit of magic left is the generated $spritemap-sprites
variable. I
can live with that. We no longer have to change our markup, and we can
customize how the background image is applied by supplying additional arguments
to the sprite
mixin.
In my recent project, I took this a bit further by defining my own mixin.
$spritemap-spacing: 50px
@import "spritemap/*.png"
=background-sprite($name, $repeat: no-repeat, $offset-x: 0, $offset-y: 0)
background-image: $spritemap-sprites
background-repeat: $repeat
+sprite-background-position($spritemap-sprites, $name, $offset-x, $offset-y)
// if no offsets given, set the dimensions of the element to match the image
@if $offset-x == 0 and $offset-y == 0
+sprite-dimensions($spritemap-sprites, $name)
By writing my own mixin, I not only have more control over how the sprites are used, but future developers can read this mixin and understand what is happening. Here is how I'm using it:
// simplest case; sets the background image and dimensions of the element
h3
+background-sprite(ribbonfull)
// custom offset; does not set the dimensions of the element
h2
+background-sprite(ribbonend, no-repeat, 3px, 22px)
// repeating backgrounds are possible, too
#positions
+background-sprite(doubleline, repeat-x, 0, 45px)
The generated CSS looks this:
h3 {
background-image: url('/images/spritemap-sb826ca2aba.png');
background-repeat: no-repeat;
background-position: 0 -405px;
height: 29px;
width: 295px; }
h2 {
background-image: url('/images/spritemap-sb826ca2aba.png');
background-repeat: no-repeat;
background-position: 3px -296px; }
#positions {
background-image: url('/images/spritemap-sb826ca2aba.png');
background-repeat: repeat-x;
background-position: 0 -751px; }
There are a few gotchas worth mentioning whenever you're using sprites. First,
unless every background image is oriented at the top left and fills the full
element, you're going to want some spacing between the sprites so adjacent
images don't sneak in. Play with the $<map>-spacing
configuration variable to
see what works for you. I ended up at 50px.
The other gotcha is with repeating backgrounds. By default, Compass will stack the images veritically in the sprite map. This means you can still have repeating backgrounds on the x-axis, but you'll need to ensure that any image you want to repeat is the fill width of the generated sprite map. Images that repeat on both axes (such as background textures) are not possible with sprites.
I'm pretty happy with how this has ended up. I'll definitely be using this feature of Compass in most future projects. CSS Sprites are a powerful way to increase browser performance, and now there's less friction than ever to give it a try.
A philosophical question: do you always pack all your sprites into one single sprites map? I only ask because you use your
$spritemap-sprites
within your mixin and don't allow to pass a map as variable.I think you should re-write your mixin to something like this:
Ah, and for the guys who don't know it yet: you don't have to pass all variables to a Sass mixin, you can just pass the ones you need: