1. Icons, backgrounds, logos.
  2. Scenario
  3. Manually creating a sprite image and CSS
  4. Automated sprite generation using Compass
  5. Optimizing sprites generated by Compass
  6. Further steps to reduce image requests
  7. Conclusion

Icons, backgrounds, logos.

Your site most likely has at least a few helpful icons, fancy image backgrounds or partner logos. Chances are that even your site navigation and footer make use of these images.

Each image is requested on its own from the server, causing longer wait times then necessary.

So called “sprites” come to the rescue. A sprite combines several icons (say, icon1.png and icon2.png) into a single sprite.png file. Each icon can afterwards be addressed separately via CSS classes.

The advantage: One request for sprite.png instead of N requests for icon1.png, icon2.png … iconN.png.

In case this is new to you, I recommend you catch up on the basics in this enjoyable post over at CSS-Tricks.

Scenario

One project of mine has grown over years and aggregated some images mentioned above. Let’s have a look at the requested images for a new visit of its home page:

Image requests before spriting

33 image requests, mostly for small images. There are several groups here:

  1. Some icons (from silk icon set).
  2. Browser icons in 24x12 px
  3. Browser icons in 25x25 px.
  4. Background gradients repeated on x-axis.
  5. Partner logos with hover effect.

These images are organized in separate directories already, so the goal is to create one sprite for each directory above for easier maintenance. It would be possible to merge all images into a single sprite, but for a start, I’ll just stick to these five sprites.

Manually creating a sprite image and CSS

I dislike doing tasks by hand which a machine can do faster and more reliably for me. Sprite generation is one of these tasks. However, this project has neither an underlying framework nor other asset management tools to start from. An introduction of such tools is therefore a bit of an effort, so I want to make sure it’s worth it.

So we’ll test the benefit of using sprites instead of single images, taking one group of the above as the test case.

Icon requests

Let’s build a sprite out of it. To do so, open your favorite image editor, create a large enough image and paste in the icons in order:

Icon sprite

The required CSS is easy to build as well:

.icons-sprite,
.icons-chart_bar,
.icons-chart_pie,
.icons-group,
.icons-help,
.icons-house,
.icons-star,
.icons-table,
.icons-user_add,
.icons-world {
    background: url('/assets/images/sprites/icons.png') no-repeat;
    display: inline-block;
    vertical-align: -4px;
    width: 16px;
    height: 16px;
}

.icons-chart_bar    { background-position: 0 0; }
.icons-chart_pie    { background-position: 0 -16px; }
.icons-group        { background-position: 0 -32px; }
.icons-help         { background-position: 0 -48px; }
.icons-house        { background-position: 0 -64px; }
.icons-star         { background-position: 0 -80px; }
.icons-table        { background-position: 0 -96px; }
.icons-user_add     { background-position: 0 -112px; }
.icons-world        { background-position: 0 -128px; }

In the page HTML source, the calls have to be changed as well, from

<img src="/img/icons/house.png" alt="" />

to

<i class="icons-house"></i>

Alright, having these in place, let’s deploy and check what results we get!

Image requests after using icon sprite

The results are promising: Our users have to make 8 requests less, and we even save 3KB of data sent - which is a 30% improvement for those icons.

Great! Now that we have “proven” usefulness, let’s do it again for the other image groups.

But wait - the manual approach has some downsides.

  1. Maintenance. If a new icon shall be added to this set, it has to be added to both the sprite image and in CSS. While this can be done without much hassle, removing an icon is a real challenge, as all subsequent background positions change on cutting it out of the sprite image.
  2. Time-consumption for creation. In the icons directory there were not nine but 28 icons, used across the whole project. Copying them into the sprite file manually and creating their appropriate CSS classes would be no fun at all.

Let’s make sprites fun again. Compass in.

Automated sprite generation using Compass

Compass is an asset management tool, probably already known to you if you’ve built (or are building) a Rails app.

It can compile SASS and SCSS stylesheets and do some more magic, and, luckily can automatically generate a sprite image from a directory!

For starters, it will create the above icons sprite for us.

I created a new assets directory to contain all files for Compass, i.e. the project directory structure related to Compass is as follows:

- project/
    - assets/
        - css/
            - screen.css
        - images/
            - icons/
                - house.png
                - star.png
                - ...
        - sass/
            - screen.scss
    - config.rb

Directory names can be changed as desired, as they are configurable in Compass’ config file, config.rb:

# config.rb

# Set this to the root of your project when deployed:
http_path = "/"
css_dir = "assets/css"
sass_dir = "assets/sass"
images_dir = "assets/images"
javascripts_dir = "assets/js"
fonts_dir = "assets/fonts"

output_style = :nested
environment = :production

All .scss files in project/assets/sass/ are compiled to CSS when running compass compile (or compass watch to re-compile on file changes).

$ cd project
$ compass compile

One piece missing for automated sprite generation: screen.scss. Look out, there be dragons:

@import "icons/*.png";
@include all-icons-sprites;

.icons-sprite {
    display: inline-block;
    width: 16px;
    height: 16px;
    vertical-align: -4px;
}

For the project above, I repeated this step for various sprite-able images. When deploying, these were my results:

Image requests after using sprites

Alright, a lot less requests (14 instead of 33), but increased download size (48.5KB instead of 38.6KB).

This is because the newly created sprites contain more images than were originally used on the page - i.e. the icons sprite now contains 21 icons. Users will benefit when visiting subsequent pages containing other icons, as the sprite is already being cached.

However, we can do something about the file size - by running image optimizers on the generated sprites.

If you wondered why I did not sprite some (gif) images - they will be handled in a different step, namely replacing gradient images with CSS3 gradients.

Optimizing sprites generated by Compass

Reducing file size is not directly built-in to Compass, but there are programmatic hooks available to influence sprite generation. In this particular scenario, we want to hook into the process after any sprite image has been generated and run PNG optimization tools on it.

For this step it is required to

  1. Get PNG optimization tools
  2. Build optimization tools into Compass compile pipeline

For this step I followed this gist, and therefore use OptiPNG, pngquant and PNGOUT in combination with PngOutBatch. So, to get started, I downloaded all executables (as I’m on Windows for this project, these were optipng.exe, pngquant.exe, pngout.exe and pngout-batch.cmd) and placed them into a freshly created bin/ directory at my project root:

- project/
    - assets/
    - bin/
        - optipng(.exe)
        - pngout.exe
        - pngout-batch.cmd
        - pngquant(.exe)
    - config.rb

To hook into sprite generation, the following code is added to the bottom of config.rb, which is a modified version of the aforementioned gist:

# config.rb

# Set this to the root of your project when deployed:
... # as above

# Sprites optimization
# see https://gist.github.com/morewry/4150334
project_path = File.dirname(__FILE__) + "/"
utils_dir = "bin/"
utils_path = project_path + utils_dir

# callback - on_sprite_saved
# http://compass-style.org/help/tutorials/configuration-reference/
on_sprite_saved do |filename|
    if File.exists?(filename)
        if (environment == :production)
            # Post process sprite image
            postprocesspng(filename, utils_path)
        end
    end
end

# fn - Post processing for pngs
# https://pngquant.org/
# http://advsys.net/ken/utils.htm & http://nicj.net/2012/05/15/pngoutbatch
# http://optipng.sourceforge.net/
def postprocesspng(filename, utils_path)
    if File.exists?(filename)
        sleep 1
        puts "      optimizing #{filename}"
        optimize(filename, utils_path + "pngquant --iebug --force --ext .png 256")
        optimize(filename, utils_path + "pngout-batch.cmd")
        optimize(filename, utils_path + "optipng -o7 -silent")
    end
end

# fn - Run optimize command line for a specified script
# https://gist.github.com/2403117
def optimize(filename, script)
    if File.exists?(filename)
        sleep 1
        puts "            with #{script}"
        command = script + " " + "\"" + filename + "\""
        system command
    end
end

To see the effects without changing any of the sprite images, compilation has to be forced:

$ cd project
$ compass compile --force

The results are impressive! Compressing those sprite PNG images reduces the size of all image requests from 48.5KB to 28.1KB, so we’re back below the starting point (38.6KB).

Image requests after using PNG optimization on sprites

Further steps to reduce image requests

In the above example, some images were left out from spriting. Those are mainly gradient background images, which will be replaced by CSS 3 gradients in an additional step.

Moreover, introducing a Compass-generated CSS file added a stylesheet request, so another step will be to merge the existing stylesheet into it.

Conclusion

I hope this post helps you with replacing single image requests by sprite images, and to get you started with spriting using Compass.

I’ll be glad to help with any questions!