UPDATE (2006-07-10): Some sections of the code have been replaced with more accessible code, which is also more search-engine friendly.
UPDATE (2009-05-28): Used a list to better organize the navigation.
Table of Contents
- Introduction
- The Traditional Way Using CSS
- Why The Traditional Way Isn’t Best
- The Change
- More Than One
- Going Beyond
Introduction
The common way to create rollover menus and images uses JavaScript; however, in this tutorial we’ll be creating the same effect without JavaScript.
Don’t get me wrong – JavaScript is not evil. In fact, it is a widely-supported way to add brilliant effects to a webpage. It’s excellent for form validation, and many websites use it for AJAX effects.
We’re creating these rollovers entirely in CSS because JavaScript can easily be disabled and CSS cannot, and CSS works in Internet Explorer 4+, Netscape 4+, Opera 4+, and all versions of Safari, Camino, Mozilla, Konqueror, and Firefox. (Note that this method will degrade well for older browsers that don’t support CSS, and it’s also more friendly to search engines and screen-readers than most CSS rollover methods.)
The Traditional Way Using CSS
Most people use the same way of CSS-only rollovers. Assume we’re using the following two images:
rollover_normal.jpg (78 x 78)
rollover_hover.jpg(78 x 78)
The traditional way to create CSS-only rollovers is like this:
CSS:
a#mylink {
display: block;
height: 78px;
width: 78px;
background-image: rollover_normal.jpg;
}
a#mylink:hover {
background-image: rollover_hover.jpg;
}
XHTML:
<a href="#" id="mylink"> </a>
Before we go any further, let’s break down this code.
a#mylink {We have created a style for a link called mylink.display: block;lets us change how high and wide we want our link to be.height: 78px;andweight: 78px;are the width and height of both images.- With
background-image:we set the backgound image of the new block we made. - When someone hovers over mylink –
a#mylink:hover– the background image of the block changes to our hover image. - Notice that we have a non-breaking space (
) between<a>and</a>as our link text. This is because some browsers won’t display the link at all if there is nothing between<a>and</a>.
Why The Traditional Way Isn’t Best
The traditional method is widely supported, but the problem is the obvious lag when someone hovers over the image. In many browsers, the hover image is not loaded into the cache until the user hovers over the image, resulting in one or several seconds before the hover image appears, leaving a sudden disappearance where the normal image was until the hover image loads.
There are two common ways to fix this. Most people use a JavaScript preloading script that loads the images into the page before it even finishes even loading the webpage. Since we are not using JavaScript, this is not the best way to achieve the effect we desire.
Another approach is to place your hover image in a hidden section of your webpage, like the following:
CSS:
a#mylink {
display: block;
height: 78px;
width: 78px;
background-image: url('rollover_normal.jpg');
}
a#mylink:hover {
background-image: url('rollover_hover.jpg');
}
#hide {
display: none;
}
XHTML:
<a href="#" id="mylink"> </a> <div id="hide"> <img src="rollover_hover.jpg"> </div>
Let me say straight-off that this is a dumb strategy. Yes, your image loads before you hover – in most browsers. However, it’s a lot of code if you have a lot of links, and if the user has a dial-up connection, the images that are displayed will probably be saved to the cache before the hidden images are, so this method is hardly an improvement the problem.
The Change
A better way to have the images load at the same time is simple – make them into one image.
rollover_both.jpg (78 x 156)
Rather than switching images this time, let’s switch between showing the top half of the image and showing the bottom of the image. This is accomplished using background-position:.
CSS:
a#mylink {
display: block;
height: 78px;
width: 78px;
background-image: rollover_both.jpg;
background-position: top;
}
a#mylink:hover {
background-position: bottom;
}
XHTML:
<a href="#" id="mylink"> </a>
Voilà – problem solved. The hover image will load as soon as the normal image because they’re the same image!
More Than One
This gets to be a lot of code when we create more than one link:
rollover_both.jpg (78 x 156)
rollover_brown.jpg (78 x 156)
Let’s code another link the same way we coded the last link, only giving it a different id.
CSS:
a#mylink {
display: block;
height: 78px;
width: 78px;
background-image: rollover_both.jpg;
background-position: top;
}
a#mylink:hover {
background-position: bottom;
}
a#mysecondlink {
display: block;
height: 78px;
width: 78px;
background-image: rollover_brown.jpg;
background-position: top;
}
a#mysecondlink:hover {
background-position: bottom;
}
XHTML:
<a href="#" id="mylink"> </a> <a href="#" id="mysecondlink"> </a>
As you can see, both links’ styles are virtually identical, which is a lot of pointless duplication. Let’s start by wrapping both links in a section called #navbar.
XHTML:
<div id="navbar"> <a href="#" id="mylink"> </a> <a href="#" id="mysecondlink"> </a> </div>
Now that we have the links grouped, we can apply some styles to all links in the #navbar.
CSS:
#navbar a {
display: block;
background-position: top;
}
#navbar a:hover {
/* Change the background position of
all links in #navbar when hovering */
background-position: bottom;
}
a#mylink {
height: 78px;
width: 78px;
background-image: rollover_both.jpg;
}
a#mysecondlink {
height: 78px;
width: 78px;
background-image: rollover_brown.jpg;
}
XHTML:
<div id="navbar"> <a href="#" id="mylink"> </a> <a href="#" id="mysecondlink"> </a> </div>
If all the images in your navbar have the same height or maybe width, you could also put that in the #navbar a section of your CSS to avoid duplication.
If you are using this effect with only one image acting as a background to link text, you only need one set of styles, since you will be using the same image for each link.
Going Beyond
Just to make things more interesting, let’s make these links look good for a search engine.
Right now, there’s just a non-breaking space ( ) for link text. If we have anything more than that, we’ll help search engine spiders determine the content of the page that the link points to, and we’ll also provide more accessible code for those with screen-reading devices. The problem is that any text will normally be displayed over top of the image.
This may be a good thing … if you want to use text above the image. If you don’t, however, you can move the text so far to the left that it’s never displayed. I chose to move the link text -3000em to the left so that the text is still part of the document, just not displayed.
CSS:
#navbar a {
display: block;
background-position: top;
text-indent: -3000em;
}
#navbar a:hover {
background-position: bottom;
}
a#mylink {
height: 78px;
width: 78px;
background-image: rollover_both.jpg;
}
a#mysecondlink {
height: 78px;
width: 78px;
background-image: rollover_brown.jpg;
}
XHTML:
<div id="navbar"> <a href="#" id="mylink">My Link</a> <a href="#" id="mysecondlink">My Second Link</a> </div>
Now, to make it even more semantic, I move these links into a list. Notice how I removed the list’s default styles and display using CSS. I also made the list the same height as the links so that the floating list items inside it don’t overflow their container.
CSS:
#navbar ul {
list-style: none;
margin: 0;
padding: 0;
height: 78px;
}
#navbar li {
float: left;
}
#navbar a {
display: block;
background-position: top;
text-indent: -3000em;
}
#navbar a:hover {
background-position: bottom;
}
a#mylink {
height: 78px;
width: 78px;
background-image: rollover_both.jpg;
}
a#mysecondlink {
height: 78px;
width: 78px;
background-image: rollover_brown.jpg;
}
XHTML:
<div id="navbar">
<ul>
<li><a href="#" id="mylink">My Link</a></li>
<li><a href="#" id="mysecondlink">My Second Link</a></li>
</ul>
</div>
If you want, you can create more than two states. You could, for instance, have a normal state, hover state, highlighted state, and highlighted hover state, all in one image. However, if you want more than three states per link, instead of using background-position: top; and background-position: bottom;, you’d have to use pixel values instead, such as background-position: 0 -78px; (the first number counts from the left and the second counts from the top).
