Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Compass and CSS Sprites, Explained

Compass and CSS Sprites, Explained

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"
<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"

  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

// custom offset; does not set the dimensions of the element
  +background-sprite(ribbonend, no-repeat, 3px, 22px)

// repeating backgrounds are possible, too
  +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.

jmuheim commented Jun 4, 2013

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.

jmuheim commented Jun 4, 2013

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:

= background-sprite($map, $name, $repeat: no-repeat, $offset-x: 0, $offset-y: 0)
  background-image: $map
  background-repeat: $repeat
  +sprite-background-position($map, $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($map, $name)

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:

+background-sprite($input, $offset-x: 13px, $offset-y: 14px)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment