Dave Shea recently published an article on A List Apart (ALA), CSS Sprites2 – It’s JavaScript Time’, about how to use jQuery to create the effect of animated rollovers on navigation items.
The technique he outlines makes use of the same image replacement method as outlined in ALA’s original Sprites article. The problem with this method however is that it uses a large negative text-indent to remove the default text from screen, and with images turned off in the browser, you don’t see anything. This has accessibility implications not only from the perspective of those with disabilities, but also for those who deliberately turn images off, i.e. people on slower connections or those using handheld devices who are trying to limit the amount of information downloaded to their phone.
When I do use image replacement, I prefer a method which leaves the text on screen when images are turned off – the Gilder Levin Ryznar Jacoubsen IR method, which I’ve written about before. Having been using jQuery quite a bit myself lately, I thought I’d see if I could come up with a similar implementation which would let me use the image replacement method I prefer.
Here’s my working example.
The mark-up is essentially the same as that Dave has used:
<ul id="nav">
<li id="n-home"><a href="#home"><em></em>Home</a></li>
<li id="n-about"><a href="#about"><em></em>About</a></li>
<li id="n-services"><a href="#services"><em></em>Services</a></li>
<li id="n-contact"><a href="#contact"><em></em>Contact</a></li>
</ul>
except that I’m using IDs on my list items and rather than having a class on the <ul> to identify the current page, I’ve got an ID on the body. The CSS looks like this:
#nav {
width: 401px;
height: 48px;
margin: 0;
padding: 0;
background: url(images/animated-nav.png) repeat-x;
list-style: none;
overflow: hidden
}
#nav li {
position: absolute;
overflow: hidden;
font-size: 1em;
}
#nav li, #nav li * { height: 48px }
#nav a { display: block }
#nav em, #nav span {
display: block;
position: absolute;
top: 0; left: 0;
z-index: 1;
background: url(images/animated-nav.png) no-repeat;
cursor: pointer;
}
#nav span { display: none }
#n-home { left: 23px }
#n-home, #n-home * { width: 77px }
#n-home em { background-position: -23px 0 }
#n-home:hover em, #n-home span, #home #n-home em { background-position: -23px -49px }
#n-about, #n-about * { width: 83px }
#n-about { left: 99px }
#n-about em { background-position: -99px 0 }
#n-about:hover em, #n-about span, #about #n-about em { background-position: -99px -49px }
#n-services, #n-services * { width: 98px }
#n-services { left: 182px }
#n-services em { background-position: -182px 0 }
#n-services:hover em, #n-services span, #services #n-services em { background-position: -182px -49px }
#n-contact, #n-contact * { width: 98px }
#n-contact { left: 280px }
#n-contact em { background-position: -280px 0 }
#n-contact:hover em, #n-contact span, #contact #n-contact em { background-position: -280px -49px }
#nav .over { text-indent: -999em }
#nav .over em { background-image: none }
The only additional rules added to get the animated effect to work are:
#nav span { display: none }
#nav .over { text-indent: -999em }
#nav .over em { background-image: none }
And the javascript:
$(document).ready(function(){
// Get the ID of the body
var parentID = $("body").attr("id");
// Loop through the nav list items
$("#nav li").each(function() {
// compare IDs of the body and list-items
var myID = $(this).attr("id");
// only perform the change on hover if the IDs don't match (so the active link doesn't change on hover)
if (myID != "n-" + parentID) {
// for mouse actions
$(this).children("a").hover(function() {
// add a class to the list item so that additional styling can be applied to the <em> and the text
$(this).addClass('over');
// add in the span that will be faded in and out
$(this).append("<span></span>");
$(this).find("span").fadeIn(400);
}, function() {
$(this).removeClass('over');
// fade out the span then remove it completely to prevent the animations from continuing to run if you move over different items quickly
$(this).find("span").fadeOut(400, function() {
$(this).remove();
});
});
// for keyboard actions
$(this).children("a").focus(function() {
if ($(this).attr('class')!='over') {
$(this).addClass('over');
$(this).append("<span></span>");
$(this).find("span").fadeIn(400);
}
});
$(this).children("a").blur(function() {
$(this).removeClass('over');
$(this).find("span").fadeOut(400, function() {
$(this).remove();
});
});
}
});
});
Essentially, what’s happening is that javascript is being used to append an empty <span> to each anchor which is set to display: none by the CSS on page load, and when the mouse hovers over it, the <span> is animated to fade in.
Except that it’s not quite that simple. Because there’s already an empty <em> in each anchor which handles the rollover effect if javascript isn’t available, and because the change of the background-image on hover happens immediately, you don’t really notice the fading in of the hover state of the image because it’s fading in over the top of an image of itself.
So what I needed to do was to remove the background-image from the <em> when each anchor was hovered over – #nav .over em { background-image: none }. But this would then leave the text of each link visible, so I also needed to use text-indent to remove it from view – #nav .over { text-indent: -999em }. Using a negative text-indent was one of the reasons I shied away from the technique used in the ALA article, but at least in this example, the text is still visible in its normal state if images are turned off and only disappears when hovered over.
The javascript for this example is a bit lighter than the ALA example and it ticks a couple more accessibility boxes in that the animated effect will work for both mouse and keyboard-only users with both :hover and :focus events, and the text of the links is still visible with images turned off, but as mentioned above, it does still need to use text-indent: -999em which I would’ve preferred not to have used. So if anyone has any ideas about how to remove the need for text-indent, I’d like to hear them.
Update
I noticed recently that Firefox has a problem when clicking the links in that it was layering a second empty span over the top of the first one applied by the part of the script that produces the effect for when the link receives focus only rather than hover. The second empty span was preventing the browser from redirecting to the anchor destination. So I’ve modified the script to check whether the class of over has already been applied when the link receives focus and only apply the class and the extra span if it hasn’t been.







That is quite nice, the only thing stopping me from using it is if you mouse over the buttons quickly (move it left to right quickly multiple times and then away) the effect keeps running for a while. Can you work out a way to stop the animation from doing this?
Hi Ryan, thanks for pointing that out.
I’ve now updated the script so that it only adds the extra span when you
:hover/:focuson a link and then removes it again when you move away.This prevents the animations from continuing to accumulate for each link you hover over and playing out after you’ve stopped moving over the links.
That’s great thanks for updating it.
John, that’s outstanding. Image replacement, keyboard focus… much better!
Thanks Mike.
If I could only find a way for the text not to be indented on hover/focus, I’d be happy with it.
Hi, nice post
John, Very Nice work… I like the improvements you made to Mark’s example. I am wondering though why the Home button remains in the selected mode…on both yours and Marks. Is there a reason for that? I would like it if the only button in the “selected” or “active” mode was the actual button selected. Of course the Home button should be selected by default when the page first loads. Am I missing something?
Thanks Again!
oops! I meant Dave not Mark… I know a Mark Shea and I confused the two… my bad!
Because the active link is set by an ID on the body tag. If the links actually went somewhere to different pages, there’d be a different ID on each one and the active link would change, but because you’re always on the same page, the body ID never changes. I suppose I could’ve added to the javascript to change the body ID dynamically for demonstration purposes, but it wouldn’t be required in a real environment, so it’s better left out.
Ah… Got it! So here’s what I am trying to do… I am loading page content dynamically into a using the jQuery plug-in Ajaxify. All of the content is loaded into a div. Therefore in this case my menu remains while the content changes. Is there a way (I’m sure there is) to update the menu dynamically when the contents change… since the body tag never changes I am assuming that it would require looking for an ID tag within the div. Any thoughts?
Presumably each piece of content has its own ID? Can you add a class to the menu unordered list or its container based on the ID of each individual item?
I think it could be possible… currently the content div is just named ID=”content” on every page that is loaded. So for instance, the way Ajaxify works… I have my index.html page. I have my menu:
| Home | About | Services | Contact |
The content of home.html and index.html are identical, and each page has complete info for that page so that is can break down easily if Javascript is off. But when JS is on, Ajaxify looks for the “content” div of the file being loaded and loads only the contents of the content div.
In your code, it’s looking for the ID of the body tag, but perhaps it could be altered to look for the first part of the file name being loaded or perhaps a class on each menu button that would tell the script which page is being loaded and then add an “active” class to the coresponding menu button…while removing the “active” class to any buttons that previously had the class. I’m just not versed enough in jQuery to do this easily… I will look through some other codes to see if I can understand how this might be accomplished.
Unfortunately, I don’t know enough about how Ajaxify works to suggest a solution.
This looks great but I’m new to everything javascript so my question might be a bit stupid: in Firefox (Mac & PC), the only link that works is Home. Clicking on the other doesn’t lead to the #contact etc - you can see this in the address bar. It works in other browsers though - eg Safari, IE 7. If I disable javascript in Firefox, the links work. What am I missing?
Thanks for your help in advance and also thanks for sharing this with us.
great design..
I know this is an old post but is there anyway to get a current state and active (click) state to trigger similar to Dave Shea’s original article?
Much Thanks..
Hi Stuart, sorry for the late reply. I’ve only just had a closer look at what you were talking about and it seems like it might be a bug with Firefox. Replacing #xxxx with a real URL also shows similar behaviour except that you do eventually get to the destination of the link. I haven’t quite worked out what the trigger is though as it involves clicking more than once and moving the mouse away from the link and I haven’t narrowed it down to the exact combination. But as you say, it works fine in other browsers, and it also works in Firefox if you tab into the link with the keyboard, which makes me inclined to believe it’s a Firefox bug.
It would require someone with more experience with jQuery and javascript to drop by to provide the solution though.
@c - the current link is set with an ID on the body which matches up against the ID of each list item. And for the active state, it would be the same process as Dave has used - create an image that includes an extra state and move it into position on :active.
Hi John,
Thanks for your response and great article! I did manage to get the (Current) state to work as you intended and just had to isolate the BG position to trigger a third image.
like so…
#home #n-home em { background-position: 0 -78px; cursor: default; }
As for the (Active) state I haven’t had as much luck.
Here is what I am trying to use:
#n-home:active em { background-position: 0 -117px }
but this does not seam to work when clicked. What am I missing?
Much Appreciated.
It’s because it should actually be #n-home a:active em. The ID is applied to the list item, not the anchor and there is no active state for list items, although there are hover and focus states. Normally, creating a hover effect on non-anchor elements would mean this wouldn’t work in IE6, but jQuery is providing that added level of support which means for the purposes of this demonstration it does.
Hi John
Thank-you for your articles. I’ve been bookmarking them all! I’m a bit of a noob and i have problems with Rollover form buttons. They work except i cannot get them to show the text *when images are off* I’ve searched high and low and haven’t been able to find a solution.
Here is my CSS
.JoinBtn {
height:24px;
width:82px;
background:url(../styleImages/buttons/joinnow1.gif) no-repeat;
margin-left:19px;
cursor:pointer;
}
.JoinBtnHover {
height:24px;
width:82px;
background:url(../styleImages/buttons/joinnow2.gif) no-repeat;
cursor:pointer;
margin-left:19px;
border:none; }
and this is my mark up
I have tried everything! i want to make the site accessible for readers who wish to have their images off.
Your help would be much appreciated.
Thank-you and keep up the good work!!!
Jim.
oops this is my mark up
input name=”submit” value=” ” type=”submit” class=”JoinBtn” onmouseover=”this.className=’JoinBtnHover’” onmouseout=”this.className=’JoinBtn’”
Hi Jim, I tend to use input type=”image” or images inside <button>s in these situations.
thanx John
I’ve tried that but i still see the *text* over the image. What i’m trying to do is show the button image with its hover effect now working but also have the text “submit” behind the image so when images are turned off the user can see the text “submit”
I’m sure there has to be some work around this…