Making sprites sucks. They’re incredibly useful for web development since they keep assets minimal and HTTP requests (and thus loading times) low, but they are a huge time sink: somebody has to Tetris together a bunch of tiny images into one file so that they fit in as little space as possible, then figure out all the dimensions and coordinates of every little icon, make up class names for every single one, then write a bunch of verbose CSS so you can actually use the individual icons in the sprite. What a pain.
I used to argue with our designers about who should build sprites. But even if I escaped the burden of creating one, writing the related CSS was painful too. Adding a new icon to a sprite was regularly a source of mental anguish. Our main sprites can easily have over 50 different images and icons in it — when you run a huge website like the Behance Network or a complex web application like Action Method, it’s not unusual to be dealing with that many different icons. So we changed our spriting process completely.
The red arrows are pointing to icons used in AMO. This is only a fraction of the icons in the sprite.
Now, we let two applications do the hard work for us: Slicy (previously known as Layer Cake), a Mac application by MacRabbit that automatically slices and exports your Photoshop layers based on their names, and Compass, a popular Sass framework with a built-in sprite generator.
Our work flow is brilliantly simple and quick now. Our designers name their layers as they go along, then simply drop their PSD into Slicy, which generates individual images based on the layer names. The generated images are then handed off to the developer, and the designer’s contribution to the sprite is now completely done. (Seriously.)
The layers with .png appended so Slicy knows what to export.
For the developer, the process is just as simple thanks to Compass. All it takes is a little bit of magic: in a Sass file, tell Compass where your individual images are, set a couple customization variables, save your code and let Compass stitch all of your images into one sprite. Along with your new sprite, Compass generates the CSS for every individual image’s class name, dimensions and coordinates. The only thing left is to put class names in your markup. I kid you not. It’s that easy.
The individual images, the compiled sprite, and the generated CSS.
Here’s the technical breakdown for getting Compass to generate a sprite:
1. Our file structure looks like this:
config.rb
css/
sprites.css
images/
sprite/
dashed-border-add-file.png
icon-actionstep-focus.png
icon-actionstep-overdue.png
icon-actionstep-today.png
icon-activity-accept-selected.png
... all the rest of the individual images Slicy made for us ...
sass/
sprites.scss
2. The config.rb for our Compass project designates where our Sass, CSS and images are stored. We also have a hook for automatically renaming and moving sprites when they’re done being compiled.
http_path = "/" # not used in this exercise
css_dir = "css"
sass_dir = "sass"
images_dir = "images"
javascripts_dir = "javascripts" # not used in this exercise
output_style = :expanded
sass_options = {:unix_newlines=>true}
line_comments = false
preferred_syntax = :scss
# Rename sprites to remove the Compass-generated hash and move it up 1 directory
on_sprite_saved do |filename|
if File.exists?(filename)
FileUtils.mv filename, filename.gsub(%r{-s[a-z0-9]{10}\.png$}, '.png').gsub('images/../images/', '')
end
end
3. In sprites.scss, we set some magic variables and call some magic functions – they’re magic because their existence is based on the name of the folder where your images are stored. Our images are stored in a folder called “sprite” (see our directory structure above).
$sprite-sprite-base-class: '.sprite'; $sprite-layout: smart; $sprite-sprite-dimensions: true; @import "compass/utilities/sprites/base"; @import "../images/sprite/*.png"; @include all-sprite-sprites;
A quick breakdown of the few options we set:
$sprite-sprite-base-class: Tells Compass to define one additional class with the sprite as its background-image. Use as$<folder name>-sprite-base-class.$sprite-layout: This tells Compass how we want our images laid out within the sprite. The options arevertical,horizontal,diagonalandsmart. Smart means that it packs all the images in together as tight as possible so that the footprint of the final sprite is as small as possible. Use as$<folder name>-layout.$sprite-image-dimensions: Tells Compass to include the dimensions of each individual image in the CSS along with its coordinates. Use as$<folder name>-image-dimensions.
Note that there are many other customizations you can set for your sprite, but these 3 did the job for us. For the full list of options, check out the documentation at http://compass-style.org/help/tutorials/spriting/customization-options/.
The final step is to @import the Compass sprite module and all of the images we want compiled into a sprite. Then, by calling the magic function all-sprite-sprites (all-<folder name>-sprites), Compass will dig into our sprite folder, stitch everything together, drop a CSS file into our css folder and a complete sprite into our images folder. The result is:
- A complete sprite.
- A complete CSS file. Ours is big (look at all those icons!), so here’s a sample:
.sprite, .sprite-dashed-border-add-file, .sprite-icon-actionstep-focus, .sprite-icon-actionstep-overdue, .sprite-icon-actionstep-today { background: url('/images/../images/sprite-s134b848ff6.png') no-repeat; } .sprite-dashed-border-add-file { background-position: 0 -177px; height: 145px; width: 342px; } .sprite-icon-actionstep-focus { background-position: -293px -64px; height: 27px; width: 24px; } .sprite-icon-actionstep-overdue { background-position: -269px -64px; height: 27px; width: 24px; } .sprite-icon-actionstep-today { background-position: -245px -64px; height: 27px; width: 24px; }
At this point, it’s important to note that obviously the path to your image is wrong if you’ve kept our config.rb function to rename and move the file. However, I’m totally okay with this, because I don’t love that first rule anyhow (too many selectors for my taste), so the first thing I do after compiling a sprite is change it to be simpler and more useful:
.sprite {
display: inline-block;
background: url('../sprite.png');
}
Now, for any element that should be an icon, I just apply two classes: sprite and whichever class references the icon I’m looking for. If you ever need to add any more icons to your sprite, just drop the images into your folder and let the sprite re-compile. All the existing class names stay the same, so any new background positions are automatically reflected. You don’t have to change any of your markup.
Spriting has never been more blissful.
If you have any questions about anything in this blog post, or you just want to talk about Sass and Compass, leave a comment below or follow me on Twitter at @jackiebackwards.
You should look into inline-image with SASS. It encodes your sprites to base64. With gzip compression you can get an even smaller footprint.
Is there any way to put images from two folders in the same sprite image?
I’ve been absent for a while, but now I remember why I used to love this website. Thanks , I
Hi would you mind stating which blog platform you
Hey there would you mind letting me know which web host you’re working with? I’ve loaded your blog in 3 completely different internet browsers and I must say this blog loads a lot faster then most. Can you suggest a good web hosting provider at a reasonable price? Kudos, I appreciate it!
This has to be one of my favorite posts! And on top of thats its also very useful topic for newbies. thank a lot for the info!
Hi I just wanted to say that this blog was a great resource for me in improving my experience with using compass to create sprites!
One thing I wanted to add is that while I agree with not liking the huge string of selectors it attaches to the background image, I also do not like having to add the extra “sprite” class to every element. So I went with [class*="sprite-"] and get the same result with no extra markup. I realize the potential downside to this if that ever matched an unintended element, but there is currently only myself and one other developer and proper comments are in place.
I would be interested in hearing everyone’s opinion.
i really liked your post, i couldnt seem to find much on the internet