CSS sprites are now almost a decade old. The core concept of the technique – creating a “panel” of images that are displayed as a background in links and moved to show different icons – is enshrined in thousands of web pages and dozens of frameworks.
As a concept, CSS sprites are very powerful: one image loaded for all buttons on a site cuts page loading times by eliminating multiple server requests for separate files. But practically, I’ve always had several issues with them:
- Sprite panels are complex to make, requiring at least two different images for each icon state.
- There is no
altattribute for background images: so if the sprite does not load, there is nothing to click on in the navigation. - Similarly, without an
altattribute CSS Sprites are not accessible. - Without alternative text, search engines lack an understanding of the purpose of links.
CSS sprites could not be animated, unless one tried to turn an animated GIF into a sprite (:: shudder ::)
- Making two versions of each icon doubles the panel size, increasing load time and complicating the CSS.
- The CSS to make a sprite work was somewhat complex, requiring multiple sets of coordinates.
In considering the redesign of this blog (currently under development) I very much wanted to update the idea of CSS sprites for the 21st century, eliminating the drawbacks of the traditional approach. I call my solution “CSS3 Sprites”. You can see an oversized example in the header image for this article.
The process to create a CSS3 sprite panel is just nine steps:
Make a sprite panel as you would traditionally, but the with icons drawn in a single state only (a monochrome theme will probably work best.). Optional: make the sprite panel twice as large as what will be actually displayed on the page.
Export the panel. If you’ve taken the optional step above, export the panel in two sizes: one at the final size of the collective icons as displayed by default on the page, say 150px × 150px, and another twice as large (ie. 300px by 300px). For this example I’ll call the first image temp-sprite-panel and the second sprite-panel. (I’d also recommend that you export the image(s) as transparent PNG files for the most flexibility).
Create an unordered list for the navigation. (You might want to look at examples I’ve talked about previously in this blog if this concept is unfamiliar to you.)
Place the
temp-sprite-panel.pngfile as animgtag inside links that are within each item in the list, as you normally would. Includealtandtitleattribute values that are appropriate for the icon that will be visible at that point.Attach a
classto the unordered list. Theclasswill be used for general shared properties of all uses of the sprite panel.At this stage, our HTML code should look something like this. (I have removed
titleattributes and values for clarity).- <ul class="sprites">
- <li>
- <a href="#"><img src="temp-sprite-panel.png" alt="RSS" /></a>
- </li>
- <li>
- <a href="#"><img src="temp-sprite-panel.png" alt="About" /></a>
- </li>
- <li>
- <a href="#"><img src="temp-sprite-panel.png" alt="Book Mode" /></a>
- ></li>
- </ul>
Right now, each list item link contains a copy of the entire visible sprite panel image, with the list items positioned one underneath the other. We are going to put the list items side by side while reducing the
opacityof the image used in each link, to create a default “inactive” state for every icon. In our CSS:- ul.sprites li { display: inline; }
- ul.sprites li a img { position: absolute; opacity: 0.4;
- width: 150px; height: 76px; }
(Note that I've placed the size of the
temp-sprite-panel.pngfile in the CSS).Next, we will visually restrict each image to show only the appropriate icon by using the
clipproperty.cliprequires that its element be positioned absolutely, which is why we applied it in the CSS above. We’ll apply the CSS by referencing thealtattribute of each sprite.Remember that
clipuses offsets from the top and left and edges of the image to determine which portion to show. You may want to open up your sprite panel in PhotoShop to determine these numbers. For our example, they might be something like:- img[alt="RSS"] { clip: rect(54px,21px,76px,0px); }
- img[alt="About"] { clip: rect(54px,47px,76px,25px); }
- img[alt="Book Mode"] { clip: rect(54px,75px,76px,48px); }
Because clipped images have no “height” per se, we need to set a
heighton the navigation list. This should be the same as the height of all uses of CSS3 sprite navigation on our site (as the icons are all the same height), so we can put that on ourclassfor our list:- ul.sprites { height: 27px; }
With everything now in place, the last thing we need to do is create the “active” state for each icon, via a
hoverpseudo-selector:- ul.sprites a:hover img { opacity: 1; }
That’s it! Each sprite is now accessible, as each has its own unique alt and title. If you wanted to animate the sprite transition, just put the appropriate vendor prefixes and values in the CSS for the image:
- ul.sprites li a img { position: absolute; opacity: 0.4;
- width: 150px; height: 76px;
- -webkit-transition: all 0.5s linear;
- -moz-transition: all 0.5s linear;
- transition: all 0.5s linear;
- }
If you want to retain greater image quality during page zooms of the sprite panel on browsers or mobile devices, substitute sprite-panel.png for temp-sprite-panel.png in the HTML, without changing the CSS. The larger image will be shrunk down to the size of the former, smaller panel, then the clip(s) will be applied. When the page zooms, each icon will look sharper.