CSS Rollover Menu with No JavaScript

This week I went on a little Twitter rant about an HTML web site design I inherited last week and was required to turn into an Asp.Net project.  I won't go into my rant on table based layouts as my good friend Lance Fisher pointed me to a definitive answer.... http://shouldiusetablesforlayout.com/.

After I got over the table based layouts I encountered this old school code for performing rollovers on the sites man menus.

 <a href="index.html" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Home','','images/home_over.png',1)"><img src="images/home_out.png" name="Home" width="90" height="46" border="0" id="Home" /></a>
 <a href="practice.html" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Practice','','images/practice_over.png',1)"><img src="images/practice_out.png" name="Practice" width="90" height="46" border="0" id="Practice" /></a>
 <a href="physicians.html" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Physicians','','images/physicians_over.png',1)"><img src="images/physicians_out.png" name="Physicians" width="90" height="46" border="0" id="Physicians" /></a>
 <a href="patients.html" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Patients','','images/patients_over.png',1)"><img src="images/patients_out.png" name="Patients" width="90" height="46" border="0" id="Patients" /></a>
 <a href="contact.html" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('Contact','','images/contact_over.png',1)"><img src="images/contact_out.png" name="Contact" width="90" height="46" border="0" id="Contact" /></a>

After seeing this code copy and pasted in every single page that we received from the designer (and taking a moment to cry) I went about cleaning up, I just can't work in copy and paste environment like this anymore.

Also, notice that all the menus where images, even though in reality they were just text. This made my life a little bit harder but I guess I'll let that one go for now.

Create a Master Page

First I created a master page and quickly replaced the repeated code about with something cleaner.

<ul class="navMenu">
    <li id="HomeMenu"><a href="home.html">
        <img src="/Content/images/home_out.png" alt="Home" />
    </a></li>
    <li id="PracticeMenu"><a href="Practice.html">
        <img src="/Content/images/practice_out.png" alt="Practice" />
    </a></li>
    <li id="PhysiciansMenu"><a href="Physicians.html">
        <img src="/Content/images/physicians_out.png" alt="Physicians" />
    </a></li>
    <li id="PatientsMenu"><a href="Patients.html">
        <img src="/Content/images/patients_out.png" alt="Patients" />
    </a></li>
    <li id="ContactMenu"><a href="Contact.html">
        <img src="/Content/images/contact_out.png" alt="Contact" />
    </a></li>
</ul>
 
The key changes are:
  • Added a list to layout the menu items
  • Gave each menu item its own id
  • Default each link content to be the out, or not selected, image
  • Removed all the inline formatting and JavaScript calls

This is great I can actually look at this without cringing.

CSS Styles

The next step was to create the necessary CSS styles to implement the rollovers for the menu items. The plan behind the approach we chose to use was to set the content of the link to be the non-selected image and the background of the link to be the selected image.  When the user hovers over the link then we will hide the link content (the non-selected image) resulting in the background image  being displayed (the selected image).  We're also going to use CSS to enforce some of the other formatting like height and width that were originally declared inline.

.navMenu
{
    list-style-type:none;    
    padding: 0px 0px 0px 0px;
    margin: 0px 0px 0px 0px;
}

.navMenu li
{
    float:left;
    padding:0;
    margin: 0;
    width: 90px;
    height: 46px;
}

.navMenu li img
{
    width:100%;
    height:100%;
    border:0;
}

.navMenu a, .navMenu a:link, .navMenu a:visited
{
    display:block;
}

.navMenu a:hover img
{
    visibility:hidden;
}

#HomeMenu
{
    background-image: url("/Content/images/home_over.png");
}
#PracticeMenu
{
    background-image: url("/Content/images/practice_over.png");
}
#PhysiciansMenu
{
    background-image: url("/Content/images/physicians_over.png");
}
#PatientsMenu
{
    background-image: url("/Content/images/patients_over.png");
}

#ContactMenu
{
    background-image: url("/Content/images/contact_over.png");
}

The first 4 .navMenu entries are nothing too exciting. Basically we're just formatting our list menu. the interesting line is what we do on the a:hover event.

.navMenu a:hover img
{
    visibility:hidden;
}

What we're saying here in CSS speak is: "When the user moves their mouse over a link in .navMenu we want to hide any images inside of that link." This hides the image allowing us to see the background image then redisplays the image when the mouse is moved off of the link.

The second interesting things that we're doing is setting the selected image for each link item by using the background property.

#HomeMenu
{
    background-image: url("/Content/images/home_over.png");
}

This is the part I like least about this solution because for each menu I need in my web site I have to add an entry to my CSS file. In this particular site the menus are static so that's not a big deal but if we were dynamically generating our menus we would definitely want to generate these pieces of the CSS file as well.

Setting the Selected Page Menu

The last thing we have to take care of is showing which menu is active. When the user is on the Home page we want the home menu to be selected and when the user is on the Contact page we want the Contact menu to be selected, etc.

We can accomplish this functionality by adding some custom CSS to each page that hides the default image for the menu item that we want to show as selected.

#ContactMenu a, #ContactMenu a:link, #ContactMenu a:visited
{
    visibility:hidden;
}

 

This will result in the selected menu always showing the background image, which in our case is indicates that the image is selected.

Conclusion

A little CSS can go a long way.  This is much cleaner than spattering that 'MM_swapImage' call everywhere and this approach gets even easier if you're not using images for displaying your menu items.

Technorati Tags:

Thursday, August 13, 2009 2:27 PM

Comments on this post

# re: CSS Rollover Menu with No JavaScript

Requesting Gravatar...
I think my eyes are bleeding from reading those onmouseout and onmouseover attributes :)

Good changes, not only did you clean up the JavaScript, you removed the need for it! I think it's better to push stuff like this to CSS if you can.

A nice approach I use for setting the selected page is to add an id attribute to the body of each page. Then, you can push page specific CSS rules into the shared stylesheet. e.g. The rule:

body#contact #ContactMenu a {visibility: hidden;}

would only be applied to the contact page.
Left by Lance Fisher on Aug 13, 2009 7:08 PM

# re: CSS Rollover Menu with No JavaScript

Requesting Gravatar...
I like the idea of adding the id attribute to the body tag because that would remove the need to set the selected item in each view page.

But... my body tag is generated in my master page so I'd have to find a way to set that value based upon the view name
Left by Andrew Hanson on Aug 14, 2009 11:20 AM

# re: CSS Rollover Menu with No JavaScript

Requesting Gravatar...
You can set it based on the view name, but I've found that to not work well, since there can be several views with the same name. e.g. Index.

May latest approach is to add a placeholder around the start tag of the body, and replace it in the view if I want to give it an id.

Another approach is to set it with ViewData, but I don't like that since the controller is now setting the id, and I feel like it should be handled by the view.
Left by Lance Fisher on Aug 17, 2009 5:33 PM
Comments have been closed on this topic.