Yesterday was the second or third time I've attempted to use the CSS sprites feature of Compass. It's been painful each time – with the documentation leaving me with more questions than answers – 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 straight 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. We pride ourselves on building software with minimal cost of change, and the cost of change when you're manually building your sprite maps is way too high. 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. I'm not trying to be a hater here. I love Compass, I use it all the time, and most of the docs are great. This is just an area where they fall short. The docs on sprites are badly organized, confusing to read, and encourage (IMHO) poor usage.
What do I mean by poor usage? Well, let's take a look at the Spriting With Compass tutorial where they state "The simplest way to use these icon sprites is to let compass give you a class for each sprite". This means you have to go to your markup and add specific "magic" classes to every element that uses a sprite background image. No, thank you. Separation of concerns aside, this is inconvenient, inflexible, and dangerous. It assumes that any future developer will understand how Compass sprites work and just know that these special classes cannot be changed or removed.
The tutorial does go on to show an alterative method that is not dependent on classes: "In this example, this is done by using the magic my-icons-sprite mixin. Note that the mixin's name is dependent on the name of the folder in which you've placed your icons." This is certainly a little better, but it's still going to be very confusing for any developer who isn't familiar with the magic that's going on here. Good luck trying to find documentation on how the my-icons-sprite mixin works and what options are available.
What I want is a way to use sprites that requires little effort but is more
straightforward, easy to understand, and with less assumed knowledge about how
it all works. It turns out that this isn't difficult at all. It's just not
documented. Let's take this
template
as a starting point. This 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 mixin defined
here.
While not documented, there is no reason we can't use those mixins ourselves.
In fact, that's exactly what I did.
$spritemap-spacing: 50px
@import "spritemap/*.png"
@mixin 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 $offset-x == 0 and $offset-y == 0
+sprite-dimensions($spritemap-sprites, $name)By writing my own mixin instead of using a magic mixin, I not only have more
control over how the sprites are used, but future developers can read this
mixin and understand exactly what is happening. The only bit of magic here is
the $spritemap-sprites variable which is created automatically when the
sprites are imported. Beyond that I'm using regular built-in mixins whose names
are self-descriptive.
Here is how I'm using this mixin:
// 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)I'm pretty happy with how this has ended up. There are still a few gotchas
worth mentioning. 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 widest image in the sprite. Images that repeat on both axes (such as background textures) are not possible with sprites.
Thanks a lot for this very useful gist. Just dove into Compass sprites some hours ago and already felt it's unnecessarily limited (because it's not natively possible to set the y-position of background images), but your mixin just solved that. Very nice.