Roll Over, Rollovers

O’Reilly Network

In my never-ending quest to make the world a more secure and stylish place, I was given recently to devote thought to the subject of image rollovers. They're everywhere on the Web, including my personal Web site, but it's always bothered me that so simple an effect should require Javascript to accomplish. Don't we have enough to worry about in this life without having to construct pre-loading, image-swapping, faintly perverted scripts just to make a link look pretty and be interactive?

Sure, CSS lets us change text colors with a:hover, and that's great for text-heavy sites like CNN, but what about the rest of us? Especially the ones who have to cope with the demands of marketing departments? For that matter, it would be nice if authors could learn straight HTML (or xhtml) and CSS, and still be able to have buttons which flash and change. If you're into that kind of thing, I mean. (Aren't we all?)

Eventually it hit me: with a little creativity and the :hover pseudo-class, it is possible to do rollover-type effects in CSS. Some of them are less flexible than traditional Javascript rollovers, while others are more flexible but do nasty things to the document's content. Of course, traditional rollovers aren't too great from an accessibility standpoint, so how much of retreat are we talking about? Not much. To compensate, you gain the ability to easily create button states which aren't part of the usual Javascript repertoire. As a bonus, these approaches make it a lot easier to change your links however you like, because the appearance of the buttons is all in one place: your CSS.

Overall, these approaches will only work in browsers with a version number of 5 or higher, and even then there is no universal support. Some of them may even lead to mangled display in Navigator 4.x, but that's only to be expected when you take standards and push them to their limits.

So here we go with three different ways to get rollover effects using nothing but HTML and CSS. You may be surprised at just how much can be done. I know I was!

Hovering the... Background?

To varying degrees, all of these tricks will involve changing the background of an element. The first one we'll try will look just like a traditional rollover to the user. The key is to take the various link states and make them backgrounds of another image. No kidding!

First we'll set up the HTML. Just bear with me on this one. First we'll need a completely transparent GIF image. Its size doesn't really matter, although you probably don't want to make it too big. Even a single-pixel image will do, so long as it's transparent. The key, really, is that every pixel in this image must be transparent. Now we'll use this blank image as the basis for all of our various buttons. Each button will be enclosed with a hyperlink-- each of which has a unique identifier.

<a id="home" href="home.html"><img src="blank.gif" alt="Home"></a>
<a id="links" href="links.html"><img src="blank.gif" alt="Links"></a>
<a id="about" href="about.html"><img src="blank.gif" alt="About"></a>

Now it gets fun. We want to have the background of each image (which we'll assume to be the same size) to use its own unique background. Thus:

a img {height: 35px; width: 70px; border-width: 0;}
a#home img {background: url(home.gif) top left no-repeat;}
a#links img {background: url(links.gif) top left no-repeat;}
a#about img {background: url(about.gif) top left no-repeat;}

It's absolutely imperative that your backgrounds be the same size as the transparent button you've created, or else this won't look right. Also, if you want borders around your images, then don't set the border width to zero as I did.

Okay, fine, we have a bunch of images which look like normal images, but were in fact constructed through some convoluted method. It only seems convoluted right now, however. It gets less so as we add links states to the mix. Obviously, we want a hover state, so that the buttons get highlights as me move the mouse pointer over them. So next we add these rules:

a#home:hover img {background: url(home-hover.gif) top left no-repeat;}
a#links:hover img {background: url(links-hover.gif) top left no-repeat;}
a#about:hover img {background: url(about-hover.gif) top left no-repeat;}

There we have it: buttons which will highlight as we mouse around. Note that we've associated the hover state with the hyperlink, since that's the only element most browsers will recognize as being hovered, but are applying the background to the blank image-- not the hyperlink! (As always, the rules are applied to the last element in the selector.)

Here's where it starts to get seriously cool. If we can style hovered buttons, we can also easily drop in different graphics for buttons which are active (being clicked on), or even those which have been visited. That's right, you can leverage CSS to quickly drop in buttons which indicate a user has already visited a page, and you don't have to use Javascript to walk through the whole browser history to do it. For example:

a#home:visited img {background: url(home-seenit.gif) top left no-repeat;}
a#links:visited img {background: url(links-seenit.gif) top left no-repeat;}
a#about:visited img {background: url(about-seenit.gif) top left no-repeat;}

You can see all of this at work in Example 1.

There are several other benefits with this trick. For example, you can resize your various buttons from your CSS if you want-- and never touch the HTML document. For example, let's assume that the home button should be twice as long as the other two. All we have to do is add this style:

a#home img {width: 140px;}

Because we already set its height with a previous rule (the a img rule) we can just override its width here. Of course, we could specify both values for completeness' sake, or change both of them to whatever size suits us:

a#home img {height: 50px; width: 150px;}

Remember that you'll have to recreate the various images being swapped into the background of the button if you resize it-- but that's true no matter what approach you use, Javascript or no.

All told, there are only two real drawbacks to this trick. First, all of your buttons in the source are the same file, and it's blank. Thus, if someone has CSS turned off (or their browser can't handle it), then they won't see a button at all. Second, it doesn't seem to work at all in Opera 5, and it's fairly slow in Navigator 6. In the latter case, it seems to not bother loading the images until they're needed, which can lead to slow refresh rates for dialup users. It might be possible to speed them up by doing an "invisible load' of the images-- possibly loading them into the foreground with a height and width of zero. You won't be double-loading them, but instead forcing the browser into getting the images before they're actually needed so tha tthey can be pulled out of the cache. Still, this is the kind of thing we're trying to avoid, so it counts as something of a drawback.

The benefits, however, are many. You can change the size and look of your buttons purely in CSS, which makes management easier and leaves you with a great deal of flexibility. You can put ALT text on your images, so your pages are accessible. You can use all the link states to achieve effects most people don't, because the Javascript to simulate them is much more complicated. And best of all, it's fairly readable even if you aren't a programmer.

Less Blankness, Less Flexibility

Okay, so maybe you aren't comfortable with making all of your button images blank, but you still want to do link highlighting and other stuff. If you're willing to sacrifice a little flexibility, you can have just that.

This is based on a trick I first learned from Todd Fahrner. By setting a grid of transparent pixels, you can let whatever's behind the image come partway through, giving a nice screening effect. In Todd's example, this was used to lay text over a background image, and I used exactly that effect recently in a digital Christmas card I created for my wired friends and family. But what if we mess with the background of the image itself? Consider:

That's the same image five times, each time with a different background color (check the source if you're feeling skeptical). In fact, the first image has a background color which is equal to the non-transparent background colors in the image itself. This smooths out the background color of the button, making it look solid. Of course, we can use any color or even a background image, so long as we don't mind it getting a bit washed out:

a#percent:hover img {background: yellow url(target.gif) top left no-repeat;}

With sufficiently creative buttons and backgrounds, you could get very close to effects which are indistinguishable from traditional rollovers. The trick would lie in knowing which pixels to make transparent in the foreground image. The half-screen might not be the best approach-- it might be better to make all of the background except the text (or icon) transparent, and then let the various highlighting effects shine through.

You can see this trick working in Example 2.

This approach has all of the advantages and none of the drawbacks of the previous trick, and even avoids the "all my buttons are really just blank" problem. However, since some of the foreground is visible, that part of the button can never really change. You can only alter the things around the visible portion of the button, which is why I say you have to sacrifice some flexibility. Of course, sufficient creativity can overcome any obstacle, right?

In fact, the only real drawback with this trick is that for some weird reason, Internet Explorer 5.x for Windows can't handle it. Since the trick is based on the same basic approach as the previous trick, this is hard to fathom, but it's true nonetheless. The buttons will still appear, but the background color doesn't change. Odd.

No Images, Restricted Utility

Of course, there's always the approach of styling hyperlink text itself, not involving any graphics in the process, and letting the chips fall where they may. This involves giving up some control over appearance, but there you have it. You can still do pretty neat stuff. The following two links are just plain text links with moderately complex styling. It actually isn't too out there in CSS terms, but browser support has only recently caught up to this sort of thing:

a#home, a#about {padding: 5px; border: 2px solid black; background: #66CCFF; color: #003366;
   font: bold 12px sans-serif;}
a#home:hover {background: yellow; color: red; border-color: navy;}
a#about:hover {background: yellow url(target.gif) center no-repeat; 
  color: red; border-color: navy;}
a#home:active {background: white; color: maroon; border-color: cyan;}
a#about:active {background: white url(target.gif) center no-repeat; 
  color: maroon; border-color: cyan;}

In each case, we're set some padding on the links, and given them backgrounds and borders. Then we simply change the styles for each state.

Check out Example 3 to see all this happening, but remember the older your browser, the worse it will look.

The advantages and drawbacks here are about evenly balanced, in my opinion. You get the benefit of simply styling text, so you have absolutely no accessibility problems, and you don't have to consume bandwidth with downloading a bunch of images (unless you want to toss them into the background). However, only the most recent browsers will even get close to doing this correctly. You'll be in danger of tripping over some nasty legacy bugs in Navigator 4.x, so you might want to use @import to hide the styles from Navigator 4.x (assuming you care about its problems). In addition, Opera 5 doesn't handle this quite as nicely as one might hope, seeming as it does to not honor the padding, although it's not too far off from the ideal. So while the markup in this trick is quite a bit simpler, you give up quite a bit in terms of backwards consistency. If that's a major concern, then this approach might not be right for you. If you're willing to look to the future, then it just might be your ticket.

Conclusion

As I mentally assembled the techniques described in this article, I was repeatedly surprised at how much I could do with pure CSS, and how much of it I would have classified as "Javascript only" not 24 hours earlier. I started to realize that the ability to use "visited" styles made the technique even more powerful than most Javascript rollovers, and that the ability to change link sizes and other aspects made it yet more powerful. For example, one trick I didn't describe was using border styles to make buttons look like buttons. Consider:

td.navbar a img {border-with: 2px; border-style: outset;}

You could change the style to inset when the link is active, for example. Or decide to change the color, look, and width of the borders of your buttons-- all without having to mess with the button images or text. The possibilities are almost endless.

So give way, Javascript-- we don't need you for rollovers any more! Thanks to the power of CSS, we can stop mucking up markup with hooks and tricks to make rollovers work, and drop the cryptic scripts they brought along. Anyway, we can do more, and do it better, than was possible with those scripts. Now we can get on to more powerful buttons, and Javascript can go back to whatever it is Javascript does when it's not trying to be a styling mechanism. It's about time, too.