SpriteMe (part 2)
This is the second of three blog posts about SpriteMe. The first post, SpriteMe makes spriting easy, reviews SpriteMe’s features with a summary of the performance savings across the Alexa U.S. top 100 web sites. This post talks about my experience at WordCamp that motivated SpriteMe and the logic behind how SpriteMe makes its recommendations for creating sprites. The third post, SpriteMe (part 3), discusses issues around sprite file size (and memory size) and wraps up with thoughts on major next steps.
The Motivation
I had a cool idea for my talk at WordCamp this past May: I would optimize a WordPress theme live onstage. The night before (says the procrastinator), I sat down to do a dry run. It only took me about 30 minutes to do most of the performance optimizations (Apache cache and compression settings, move scripts to the bottom, commandline YUI Compressor, etc.).
The final optimization was creating CSS sprites from the various background images in the theme. Here’s the process I went through:
- Run YSlow to find the CSS background images.
- Download the images and zip them.
- Create the sprited image using CSS Sprite Generator.
- Download the sprited image to my server.
- Edit the CSS that used one of the old background images to use the new sprite image and add a background-position using the offsets provided by Sprite Generator.
- Reload the page.
- Visually determine which backgrounds were broken.
- Repeat parts of steps 2-7 multiple times trying to find the right combination of images and background-position values.
Hours later, I had figured out a fairly good number of images to sprite together and the necessary CSS changes to retain the page’s original rendering. But it had taken waaaaay too long. That’s when I decided to create SpriteMe.
Spriting Logic
The hardest part of building SpriteMe was figuring out which background images could be sprited, and how they could be combined. Most of what I now know about sprites I learned in the last few months, so it’s important for me to document this logic and encourage CSS gurus to give feedback. Here’s the high-level logic that spriteme.js currently uses behind its sprite suggestions:
- skip images that repeat-x AND repeat-y
- Images that repeat both vertically and horizontally can’t be sprited because neighboring images would be visible.
- skip images that are very “short” and “narrow”
- A “short” background image has a height that is smaller than the height of its containing block. Similarly, the width of a “narrow” image is smaller than the width of its containing block. An image that is short and narrow can be sprited as long as there’s enough padding (whitespace) around it to make up the difference between it and its containing block. If this padding is too large, adding a short and narrow image to a sprite might result in an increase in the size of the sprite image (both file size and memory footprint) that negates the benefits of spriting. Background images that are only short or only narrow can be sprited and are addressed below.
- sprite repeat-x images of similar width together vertically
- The key with horizontally repeating images is that there can’t be any padding to the left and right of the image – anything on the sides will bleed through with each repeat. Thus, repeat-x images can be sprited in a vertically-stacked sprite if the sprite is the same width as the image. SpriteMe started by only spriting repeat-x images with the same width. I expanded that to enable spriting images of different widths, as long as a least-common-multiple of their widths could be found that was 20px or less. For example, repeat-x images with widths of 2, 3, and 9 could be sprited in an image 18px wide. This is done by replicating the 2px image nine times, the 3px image six times, and the 9px image twice. (Repeat-x images greater than 20px can still be sprited together, but only with other repeat-x images of the exact same width.)
- sprite repeat-y images of similar height together horizontally
- Repeat-y images of similar height are sprited together just as repeat-x images of similar width are sprited together. (See the previous bullet.)
- sprite “short” images together horizontally
- Imagine a background image (such as a watermark) that is dramatically shorter than the containing block – maybe the background image is 10px high and the containing block is 500px high. This image can’t be sprited vertically – it would require 490px of padding above or below it. But it can be sprited vertically. (These images are not narrow and do not repeat – those cases fit into one of the prior bullets.)
- sprite everything else vertically
- Background images that don’t fall into any of the above categories can be sprited vertically. These are images that don’t repeat AND are not short. In summary, they are about the same size as (or smaller than) their containing block, or they’re narrow
The logic above catches most of the opportunities for spriting, but I’ve thought of a few more situations where it might be possible to eliminate one or more additional image requests:
- Currently, SpriteMe puts repeat-x images in one sprite, and non-repeating images in a different (vertically-stacked) sprite, and never the twain shall meet. But repeat-x images could be added to the non-repeating sprite so long as they were the same width. (The repeat-x images could be replicated to achieve an equal width as described previously.) This would result in one fewer sprite image. The same could be done with repeat-y images and a non-repeating horizontally-stacked sprite.
- Images that are exceedingly “short” aren’t sprited. But if one such image is positioned at the bottom of its containing block, it could be placed at the top of a vertically-stacked sprite. Similarly, if it’s positioned at the top of its containing block, it could be placed at the bottom of a vertically-stacked sprite. This logic could be applied to “narrow” images, too, placing them to the left or right of a horizontally-stacked sprite. In the best case, this could eliminate an extra four HTTP requests.
- I’ve seen some images styled to repeat, but their containing block is the same size or smaller than the background image. These could potentially be sprited as if they weren’t repeating.
Finally, SpriteMe doesn’t sprite these images:
- jpegs – It’s totally feasible to sprite jpeg images, but while testing SpriteMe on popular web sites I found that the resulting sprite was much larger than the total size of the original background images (50K or more). A common cause was combining jpegs with images having 255-or-fewer colors. Until SpriteMe gets knowledge about color palette (issue #17), it skips over jpeg images.
- firewalled images – SpriteMe uses the coolRunnings service to create the sprite. This is done by sending the background image URLs to the coolRunnings service. If the URL is behind a firewall or otherwise restricted, it can’t be sprited.
- 0x0 elements – When generating sprite suggestions, SpriteMe examines the height and width of DOM elements with a background image. In some cases (drop down menus, popup DIVs), these DOM elements haven’t been rendered and have a zero height and width. If all of its DOM elements have a zero height and width, that background image is not sprited.
This is a thorough (i.e., lengthy) deep dive into the rules of spriting. I plan on expanding this write-up with figures as part of the SpriteMe documentation. My hope is that the logic behind SpriteMe will eventually be used in tools that make web sites fast by default, like Aptimize and Strangeloop Networks. But using this logic in production environments is still a ways off. That’s why it’s important to capture this logic in this post and in SpriteMe, and to solicit your feedback.
In my next blog post about SpriteMe, I’ll talk about ideas for reducing the sprite file size, and big items on the todo list.
sunnybear | 18-Sep-09 at 11:29 pm | Permalink |
Steve, logic behind SpriteMe is already used (as a part of) in CSS Sprites module of Web Optimizer, which makes hundreds of websites ‘fast by default’ (good slogan, I will remember :)
Thank you for great ideas.
Jeff | 19-Sep-09 at 1:14 am | Permalink |
Would be cool to support pseudo elements
s.Daniel | 19-Sep-09 at 7:18 am | Permalink |
My previous experiance with sprites show that grouping gif/png images with a similar color palette to one sprite and leaving the rest of the images in another sprite can reduce file size even further. Have you made tests in this direction?
Steve Souders | 19-Sep-09 at 3:19 pm | Permalink |
@sunnybear: Cool to hear SpriteMe is already helping!
@Jeff: Can you give an example?
@s.Daniel: Staying within 255 colors is key. That’s on the todo list as issue #17.
Premasagar Rose | 21-Sep-09 at 7:23 am | Permalink |
Hi Steve,
Thanks for producing this. It should save quite a bit of time in converting a design to one that uses sprites.
I recently went deep into the rules for sprite creation, on Dharmafly’s widget for BBC World Service. For that, we created 3 sprites:
PNG-8 for greyscale and transparent assets
PNG-24 for full colour
PNG-24 for full colour horizontal repeats (1px wide)
(and an extra one for the different widget platform icons).
It sounds like you’ve got most of it covered, although perhaps some other things to consider:
The “best case” is actually more than four narrow/short images. If the sprite is more of a square-like grid, which would allow you to fit several images along the sides.
If IE6 is to be supported, then PNG-8’s must be used for any transparent assets (IE6, bless it, will still only support index-transparency, not alpha-transparency). So when you add the logic for favouring PNG-8’s, you could start with those palettes that are used in images with transparency.
In most cases, it’s probably best to go for several PNG-8’s. But, when there’s a very wide colour palette, it may be better to go for a single PNG-24 instead.
It would be difficult to automate, but when the edge of one asset is the same as the edge for another, it’s actually possible to overlap them in the sprite. The BBC widget sprite uses that in a couple of places, where the bottom few pixels of one asset is identical the top few pixels of another, or where the left edge of one is the same as the right edge of another.
Strip out absolutely all meta data from the resultant PNG’s, as per smush.it and other optimisers.
– Prem
Jeff | 22-Sep-09 at 8:36 am | Permalink |
@SteveSouders, an example of a pseudo element is div::after { content:’meep’; background:blah; height:20px; width:20px; }
They are used all over the place in WebKit to style scrollbars, ranges, etc.