CSS Sprites: Image Slicing’s Kiss of Death

Back when video games were still fun (we’re talking about the 8-bit glory days here),
graphics were a much simpler matter by necessity. Bitmapped 2-dimensional character data
and background scenery was individually drawn, much like today’s resurgent pixel art.
Hundreds and later thousands of small graphics called sprites were the building blocks
for all things visual in a game.

Article Continues Below

As game complexity increased, techniques developed to manage the multitude of sprites
while keeping game play flowing. One variation saw sprites being plugged into a master
grid, then later pulled out as needed by code that mapped positions of each individual
graphic, and selectively painted them on the screen.

example sprites

And what does this have to do with the web?#section2

Everything old is new again, and though the rise of 3D games has made sprite maps
obsolete, the concurrent rise of mobile devices with 2D gaming capabilities
have brought them back into vogue. And now, with a bit of math and a lot of CSS,
we’re going to take the basic concept and apply it to the world of web design.

Specifically, we’re going to replace old-school image slicing and dicing (and the
necessary JavaScript) with a CSS solution. And because of the way CSS works, we’re
going to take it further: by building a grid of images and devising a way to get each
individual cell out of the grid, we can store all buttons/navigation items/whatever we wish
in a single master image file, along with the associated “before” and “after” link states.

How do CSS Sprites work?#section3

As it turns out, the basic tools to do this are built into CSS, given a bit of creative
thinking.

Let’s start with the master image itself. Dividing a rectangle into four items, you’ll
observe in this master image that our intended “before” link
images are on the top row, with “after” :hover states immediately below. There’s
no clear division between the four links at the moment, so imagine that each piece of text is
a link for now. (For the sake of simplicity, we’ll continue to refer to link images as
“before” images and the :hover state as “after” for the rest of this article.
It’s possible to extend this method to :active, :focus, and
:visited links states as well, but we won’t go into that here.)

Those familiar with Petr Stanicek’s (Pixy)
Fast Rollovers may
already see where we’re going with this. This article owes a debt of gratitude to Pixy’s
example for the basic function we’ll be relying on. But let’s not get ahead of ourselves.

On to the HTML. Every good CSS trick strives to add a layer of visuals on top of a clean
block of code, and this technique is no exception:

<ul id="skyline">
    <li id="panel1b"><a href="#1"></a></li>
    <li id="panel2b"><a href="#2"></a></li>
    <li id="panel3b"><a href="#3"></a></li>
    <li id="panel4b"><a href="#4"></a></li>
</ul>

This code will serve as a base for our example. Light-weight, simple markup that degrades
well in older and CSS-disabled browsers is all the rage, and it’s a trend that’s good for the
industry. It’s a great ideal to shoot for. (We’ll ignore any text inside the links for the
time being. Apply your favorite
image replacement
technique
later to hide the text you’ll end up adding.)

Applying the CSS#section4

With those basic building blocks, it’s time to build the CSS. A quick note before we
start — because of an IE glitch, we’ll be tiling the after image on top of the before image when we need it, instead of replacing one with the other. The result makes
no real visual difference if we line them up precisely, but this method avoids what
otherwise would be an obvious “flicker” effect that we don’t want.

#skyline {
    width: 400px; height: 200px;
    background: url(test-3.jpg);
    margin: 10px auto; padding: 0;
    position: relative;}
  #skyline li {
    margin: 0; padding: 0; list-style: none;
    position: absolute; top: 0;}
  #skyline li, #skyline a {
    height: 200px; display: block;}

Counter-intuitively, we’re not assigning the before image to the links at all, it’s applied
to the <ul> instead. You’ll see why in a moment.

The rest of the CSS in the above example sets things like the dimensions of the #skyline
block and the list items, starting positions for the list items, and it turns off the
unwanted list bullets.

We’ll be leaving the links themselves as empty, transparent blocks (though with specific
dimensions) to trigger the link activity, and position them using the containing
<li>s. If we were to position the links themselves and effectively ignore
the <li>s, we’d start seeing errors in older browsers, so let’s avoid
this.

Positioning the links#section5

The <li>s are absolutely positioned, so why aren’t they at the top of the browser
window? A quirky but useful property of positioned elements is that all descendent elements contained within them base their absolute position not off the corners of the browser window, but off the corners of the nearest positioned ancestor element.The upshot of this is that since we applied
position: relative; to #skyline, we’re able to absolutely position the
<li>s from the top left corner of #skyline itself.

#panel1b {left: 0; width: 95px;}
  #panel2b {left: 96px; width: 75px;}
  #panel3b {left: 172px; width: 110px;}
  #panel4b {left: 283px; width: 117px;}

So #panel1 isn’t horizontally positioned at all, #panel2b is positioned 96px to the left of
#skyline’s left edge, and so on. We assigned the links a display: block; value
and the same height as the <li>s in the past listing, so they’ll end up
filling their containing <li>s, which is exactly what we want.

At this point we have a basic image map with links, but no :hover states.
See the example. It’s probably easier to see what’s happening
with borders turned on.

Hovers#section6

In the past we would have applied some JavaScript to swap in a new image for the after
state. Instead our after states are in one image, so all we need is a way
to selectively pull each state out for the appropriate link.

If we apply the master image to the :hover state without additional values,
we make only the top left corner visible — not what we want, though clipped by the link
area, which is what we want. We need to move the position of the image somehow.

We’re dealing with known pixel values; a little bit of math should enable us to offset
that background image enough both vertically and horizontally so that only the piece
containing the after state shows.

That’s exactly what we’ll do:

#panel1b a:hover {
    background: transparent url(test-3.jpg)
    0 -200px no-repeat;}
  #panel2b a:hover {
    background: transparent url(test-3.jpg)
    -96px -200px no-repeat;}
  #panel3b a:hover {
    background: transparent url(test-3.jpg)
    -172px -200px no-repeat;}
  #panel4b a:hover {
    background: transparent url(test-3.jpg)
    -283px -200px no-repeat;}

Where did we get those pixel values? Let’s break it down: the first value is of course
the horizontal offset (from the left edge), and the second is the vertical.

Each vertical value is equal; since the master image is 400 pixels high and the after
states sit in the bottom half, we’ve simply divided the height. Shifting the whole background
image up by 200px requires us to apply the value as a negative number. Think of the top
edge of the link as the starting point, or 0. To position the background image 200 pixels
above this point, it makes sense to move the starting point -200px.

Likewise, if the left edge of each link is effectively 0, we’ll need to offset the
background image horizontally by the width of all <li>s prior to the one
we’re working with. So the first link doesn’t require an offset, since there are no pixels
before its horizontal starting point. The second link requires an offset the width of the
first, the third link requires an offset of the combined width of the first two links, and
the last requires an offset of the combined width of all three previous links.

It’s a bit cumbersome to explain the process, but playing around with the values will
quickly show you how the offsets work, and once you’re familiar it’s not all that hard to
do.

So there you have it. Single-image CSS rollovers, degradable
to a simple unordered list.

Buttons#section7

There’s no reason why we have to leave the links touching each other, side-by-side as they
were in the previous example. Image maps may be convenient in some spots, but what about
separating each link into its own stand-alone button? That way we can add borders and
margins, let the underlying background show through, and generally treat them as separately
as we need to.

In fact, the building blocks are already in place. We really don’t need to
modify our code too radically; the main change is in creating a new background image that
doesn’t continue from link to link like the last example did. Since we can’t rely on the
<ul> for placing the original background image, we’ll end up
applying it to all <li>s instead and offsetting each the same way we
offset the after states in the prior example.

With an appropriate image and a bit of spacing between each
<li>, we’ve got buttons.

Note that in this example we’ve added 1px borders which, of course, count toward the final
width of the links. This affects our offset values; we’ve compensated by adding 2px to the
offsets where appropriate.

Irregular shapes#section8

Up till now we’ve focused only on rectangular, non-overlapping shapes. What about the more
complex image maps that image slicers like Fireworks and ImageReady export so easily? Relax,
we’ve got you covered there too.

We’ll start the same way as the first example, by applying the background image to the
<ul> and turning off list item bullets and setting widths and so forth.
The big difference is where we position the <li>s; the goal is to
surround each graphical element with a box that tightly hugs the
edges
.

Again, because of the ability to use absolute positioning relative to the top left corner
of the <ul>, we’re able to precisely place our links exactly where we want them. Now
all that’s left is to set up the hover states.

Worth noting is that in this case, a single set of before and after images wasn’t enough.
Because of the overlapping objects, relying on only one after state would show pieces of
surrounding objects’ after states. In fact, it would show precisely the pieces that fall
within the link’s borders. (Easiest to just see it in action.)

How to avoid this? By adding a second after state, and carefully selecting which objects go
where. The master image in this case has split the purple and blue
objects into the first after state, and the green, orange and yellow objects into the second.
This order allows boxes to be drawn around each object’s after state without including
pieces of the surrounding objects. And the illusion is complete.

Benefits and pitfalls#section9

A couple of final thoughts. Our new CSS Sprite method tests well in most modern browsers.
The notable exception is Opera 6, which doesn’t apply a background image on link hover states.
Why, we’re not sure, but it means that our hovers don’t work. The links still do, and if
they’ve been labeled properly, the net result will be a static, but usable image map in
Opera 6. We’re willing to live with that, especially now that Opera 7 has been around for a
while.

The other concern is familiar to anyone who has spent time with
FIR. In the rare cases in which users
have turned off images in their browsers but retained CSS, a big empty hole will appear
in the page where we expect our images to be placed. The links are still there and clickable,
but nothing visually appears. At press time, there was no known way around this.

Then there’s file size. The natural tendency is to assume that a full double-sized image must be heavier than a
similar set of sliced images, since the overall image area will usually be larger. All image
formats have a certain amount of overhead though (which is why a 1px by 1px white GIF
saves to around 50 bytes), and the more slices you have, the more quickly that overhead adds up.
Plus, one master image requires only a single color table when using a GIF, but each slice
would need its own. Preliminary tests suggest that all this indicates smaller total file
sizes for CSS Sprites, or at the very least not appreciably larger sizes.

And lastly, let’s not forget that our markup is nice and clean, with all the advantages that go
along with that. HTML lists degrade wonderfully, and a proper image replacement technique
will leave the text links accessible to screenreaders. Replacing the sprite imagery is dead
simple, since all of our dimensions and offsets are controlled in a single CSS file, and all
of our imagery sits in a single image.

186 Reader Comments

  1. This is gonna save me a lot of time on some upcoming sites.

    I’m very impressed at the ingenuity shown here. Well done!

  2. Firstly, excellent article – I hadn’t thought of doing things this way.

    Here’s my attempt, which uses the initial idea along with a number of bits of advice which have been mentioned in this discussion.

    http://www.plexusmedia.co.uk/rollovermap.html

    The graphic used is
    http://www.plexusmedia.co.uk/images/hovermap.gif

    Browsers tested on
    PC – IE6, Firefox
    Mac – IE5, Safari 1.2, Firefox .08

    Lessons I’ve learnt in this are:-

    1- Because this map is a lot more complicated than the Blobs example, I needed to create 4 versions of the graphic rather than 3, as Inverness-shire was surrounded by so many other counties. The image is now a whopping 60k. However, I could go through it chopping out lots of areas in the 3 rollover maps and turning them white which would reduce the filesize. As Scott suggested earlier in this thread, I could go through the image deleting everything from the rollover state versions except the yellow bits, and then move them all up nearer the normal map and redo all the arithmetic. I’d guess the image size could be as little as 20k.

    2- I’ve seen mention of using z-index to bring some of the smaller areas to the fore. I don’t think it’s needed – what I’ve done here is shuffled the

  3. s around – the later ones seem to have the equivalent of the highest z-index.

    3- I’ve added title=”Shetland” etc to the s for alt tags

    4- I’ve used the a {text-indent: -100em;} hack as suggested by Dante-Cubed (there was something useful amongst all the vehemence) so that I can add text links in the tags. Hopefully this would give a navigable list if the styles are not used.

    The only annoyance for me is that the arithmetic is a pain in the arse. What I want now is someone to design some software to make it easy, in the same way as something like Globalscape’s Cutemap does for normal image maps.

    cheers

    Garve

  4. Comment from the sliding doors article:-

    “Switching IE to not check the page for newer content every time removed the image flicker problem.”

    Should apply here too I would think.

  5. The best reason to use, say, a 500×50 image instead of 10 50×50 images is because for every image, the server sends a bunch of headers to the client before sending the image, and the client pauses to see if the image has been cached or modified since it was last cached, and then it signals the server to continue downloading if it needs the new version.

    This is very slow when there are a lot of images. The menu on my website uses 58 images just for the buttons (there are 23 buttons, if I remember right). It takes forever to load the damn thing… but a single image would load much quicker.

    Also, everyone knows the problem of image rollovers — you need to pre-cache the rollover image in JavaScript or it won’t be loaded until you move the mouse over it. A sprite map fixes that problem.

    Otherwise it was a really great article… it occured to me that something like this would be great but I didn’t think it could be done with CSS.

  6. Hi there,
    I’ve found that Safari on OS 10.2.8 has problems with returning to the normal state after the hover. This has something to do with position:relative of the

      but I can’t figure out a way to circumvent that.

      Does someone maybe have a fix for?

      Anyway, the article is great.

  7. Chris Fam’s comment from March 12th,

    “has anyone successfully tweaked it so that images can be printed out?”

    is a major issue that, amazingly, no one has addressed. Without a work-around for this I wouldn’t recommend using this method.

    Is there a solution to this problem?

  8. Do the browsers load the background-images when they find them in the css-code? Otherwise it would cause the rollover to lag.

  9. I’ll say from the outset that I’m not especially good with CSS. 😉

    I can’t work out how to translate this to a vertical list of links, like thus:

    link 1
    link 2
    link 3

    …instead of the horizontal way this example is set out. I’ve tried changing the “left: x; width: x” to “top: x; height: x”, and the different offsets, but Safari doesn’t even register there being links there. Doing it horizontally, as demonstrated here, works fine. Thanks in advance!

  10. Yo. Anyway know the css code, for changing the state of an image defined by a CSS class, to have a border shown when the user places the mouse over the image?

  11. Yo. Anyway know the css code, for changing the state of an image defined by a CSS class, to have a border shown when the user places the mouse over the image?

  12. This article is amazing, the writing of the tutorial is also outstanding. I had it working in a complex layout in no time. The code is rock solid, very stable.

    Thanks Dave for taking the time to document this incredible technique!

  13. Fantastic!

    I’ve only been doing full CSS-based design for a few weeks, but I’m loving it!

    There’s a submission for CSSZenGarden from me in Dave’s inbox (we can but try!) and my menus are going to look fantastic now that we’ve been let in on that little gem!

    PLEASE keep up the hard work everyone- it’s our future!

    (and Dave, accept my submission… I’m sure I can find out where you live…) lol

    (I’m REALLY just kidding. really.)

    😀

    Coos

  14. Hey VW, not managed to get this fully underway myself, but did gather that you can put the image anywhere you like- left, right, top bottom…. it’s all a maths game.

    As long as the position attributes for the images in both states correspond with what you want to see in said states, your laughing.

    As for the vertical lists, I’m still trying to crack that myself, but I did see one used on http://www.CSSZenGarden.com
    Have a little browse through to see if you can spot it (I’ll have another look myself)
    (could it be ‘display: inline;’?)

    All the best 😉

  15. And what do you know,,, it was on this site! (well durr!)

    http://www.alistapart.com/articles/practicalcss/ when it loads run your search function (usually ‘ctrl’+’f’ [find]) to locate the words ‘Checking it Twice…’

    It’s right there.

    (Normally, a nice touch with css is that if an element has an ‘id’ attribute, you can use it as an anchor to that direct part of the page. That page just didn’t seem to have any!)

    Further reading on that point… http://www.w3.org/TR/REC-html40/struct/links.html#h-12.2.3
    ‘…because the W3C aren’t f***ing about…’ (quote:Me, just now.)

    I’m off to bed, but no doubt CSS won’t let me sleep AGAIN.

    zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

    Coos

  16. >Hey VW, not managed to get this fully underway myself,
    >but did gather that you can put the image anywhere you
    >like- left, right, top bottom…. it’s all a maths game.

    Aye, that’s what I figured, but I can’t work it out. Instead of having “left” and “width”, you’d think you’d have “top” and “height”, but it doesn’t work. I end up with the entire navigation image being linked as the bottom (contact) link, and the whole lot mouseovers at once…

    And setting it to inline instead of block doesn’t work either. 🙁

  17. Great article am experimenting with this right now but am having problems centering the list. I have gone to many resources and use the text-align:center trick for IE and the margin:0px auto for all others. In all other browsers except IE the list is centered.
    But in IE it is always about 30 picels off to the right forcing the user to scroll in 800×600 resolutions

    Anyone have any good links or solutions to centering this list?

  18. I am trying to learn as much as possible about CSS. I have been using Dreamweaver & Fireworks for about 5 years. I am currebnlty converting our website to all CSS. However, althoguh I keep hearing about “sprites” I have no idea what they are. It appears to be some kind of image. Could you please clarify so I can understand what you are doing. I find that those who are familier with a technology often forget that those who are still learning may not yet know all the jargon, much of which is not in the reference books.

  19. Sie sind auf der Such nach einer Kreditkarte oder suchen ein geeignetes Zahlungsmittel für den Urlaub. Mit einer Kreditkarte (englisch: credit cart) haben sie genau das richtige gefunden. Wir bieten ihnen eine Barclaycard an, diese kombiniert Mastercard und Visa in einer Karte. Wollen sie hingegen Urlaub machen und abnehmen ist Fastenwandern genau das richtige für sie. Fasten und wandern sie, Fastenwandern in den Vogesen ist gesund und macht spass. Bücher, DVDs, kann man online einkaufen, dort gibts alles günstig auch Software ohne Versandkosten. Besuchen sie Bücher, DVDs günstig kaufsonline machts möglich.

  20. I really like this idea…however I wanted to see if it really degraded to an unordered list in Netscape 4.7 (mac). It doesn’t.

    Now, I don’t intend on caring too much about this browser, but I’ve been reading Zeldman’s book and this website and have loved them both simply because they present new techniques that can transcend all the difficulties I’ve been experiencing.

    How come the sprite technique doesn’t just show up as an unordered list for me in NS4.7 Mac?

  21. Always looking for new ways in which to implement CSS and this certainly is a good one. I’ve already played about with it and found it quite easy and it seems to be an extremely good way to present things.
    Wonderful tutorial, many thanks.

  22. Hello, studying your great tutorial and trying to get to the same results I have some big problems with this button-thing. Even with a exactly same picture and your css-code (!) I don’t get to the same result… How do you count the values for the image itself and the borders and how do you get this image in the middle of the page without coding it???
    I’m willing to learn but now I need a litte or more help. Thanks a lot!

  23. Hello, in the meantime paul o’brien from sitepoint helped me with my centering-problem. Shorthand-coding is not my speciality… (margin top and bottom with px and right/left with “auto”).
    The rest of the story I solved with a little help from my partner who askes the right questions. But I still don’t understand why you used the dummy-background-image for the ul – that makes no sense to me. Also the 1px add to the width of the picture/ul I see but don’t understand.
    So if you, dave, or somebody else could explain me, I would appriciate!
    Susanne

  24. Once again you guys really make me feel like an amateur.

    Just when i thought i had a handle on it….

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career