CSS Sprites2 - It's JavaScript Time
Issue № 266

CSS Sprites2 – It’s JavaScript Time

A sense of movement is often the differentiator between Flash-heavy web sites and standards-based sites. Flash interfaces have always seemed more alive—responding to the user’s interactions in a dynamic way that standards-based web sites haven’t been able to replicate.

Article Continues Below

Lately that’s been changing, of course, with a resurgence in dynamic interface effects, helped along by JavaScript libraries that make it easy—libraries such as Prototype, Scriptaculous, Moo, YUI, MochiKit (and I could go on). It’s high time to revisit the CSS Sprites technique from four years ago, and see if we can’t interject a little bit of movement of our own.

The examples below demonstrate inline CSS Sprites2, the technique we’ll be covering in this article:

Enter the jQuery#section1

So here’s the first caveat: we’re going to lean on jQuery to make this happen. jQuery is a maturing JavaScript library that does the same neat stuff as all the other JavaScript libraries, and it has an additional advantage that lends itself particularly well to extending CSS Sprites: jQuery allows us to select elements on a page using a CSS-like syntax that we already know.

We must note the non-trivial extra kilobytes that the library will add to your initial page loads. An external JavaScript file is cacheable, of course, so it’s a one-time-only hit the first time a user comes to your site. The most compact version of jQuery weighs in at 15k. It’s unavoidable overhead, and that may be a cause for concern. If you’re already using jQuery on your site for other purposes, the overhead is a non-issue. If you’re interested in adding it solely for this technique, consider the file size and decide for yourself whether the effect is worth it. (Since Google is now hosting jQuery, you could link to their version of the library, as we do in these examples, and hope that many of your users will already have that URL cached in their browser.)

As for other JavaScript libraries? There’s absolutely no reason why you couldn’t or shouldn’t use them; consider this article an open invitation to port this technique to your library of choice—and link to your port in the comments.

Basic HTML and CSS setup#section2

The first thing we want to do is create a default, unscripted state for users without JavaScript. (We read Jeremy Keith’s article from a few years back and are now big fans of unobtrusive DOM scripting, naturally.)

We already have a CSS-only method for rollovers, so let’s begin by building our navigation to function with a basic CSS Sprites setup. And because we’re lazy, we won’t build the rollovers a second time, we’ll just reuse this foundation later and add the jQuery on top of it. We’ll get to that in a bit.

I’ll leave the hows and whys of CSS Sprites to the original article, but there are a few things to clarify below. Let’s start with the HTML. Pay close attention to this structure—we’ll refer back to it a lot:

<ul class="nav current-about">
    <li class="home"><a href="#">Home</a></li>
    <li class="about"><a href="#">About</a></li>
    <li class="services"><a href="#">Services</a></li>
    <li class="contact"><a href="#">Contact</a></li>
</ul>

Each class serves a purpose: the containing ul has a class of nav that allows us to target it in our CSS (and later JavaScript,) as well as a second class of .current-about that we’ll use to indicate, within the navigation, which page or section of the site we’re currently viewing. Each li element has its own unique class, which we’ll also use for targeting.

So far so good. Our navigation’s markup is a simple and accessible HTML list, and we have enough classes to get going with our Sprites:

.nav {
    width: 401px;
    height: 48px;
    background: url(../i/blue-nav.gif) no-repeat;
    position: absolute;
    top: 100px;
    left: 100px;
}

We’ve set the position value to absolute to change the positioning offset to the li, instead of the body element. We could have used relative instead, to accomplish the same thing while leaving the .nav element within the document flow. There are reasons to do it either way, but for now we’ll stick with absolute. For more on this absolute/relative tossup, see Douglas Bowman’s article on the subject.

The meat of our Sprites technique is in applying a background image to each of the nav items, and absolutely positioning them within the parent ul:

.nav li a:link, .nav li a:visited {
    position: absolute;
    top: 0;
    height: 48px;
    text-indent: -9000px;
    overflow: hidden;
}
    .nav .home a:link, .nav .home a:visited {
        left: 23px;
        width: 76px;
    }
    .nav .home a:hover, .nav .home a:focus {
        background: url(../i/blue-nav.gif) no-repeat -23px -49px;
    }
    .nav .home a:active {
        background: url(../i/blue-nav.gif) no-repeat -23px -98px;
    }

We’re going a bit further than the original article and defining :focus and :active states. The former is a minor addition to trigger the hover image when an anchor is subject to either the :hover or :focus states. The latter adds a new state when the user clicks on a nav item. Neither is essential, although it’s a good idea to define them both. The overflow: hidden; rule is new too—it’s just there to prevent some browsers from extending a dotted outline from the element’s position all the way off the left side of the screen to the negatively-indented text.

Example 1: Basic CSS Sprites setup.

That gets us to our real starting point—a working Sprite-enabled navigation menu, complete with currently-selected navigation items. And now to extend it.

Initializing in jQuery#section3

Note that everything below will be placed inside a jQuery function that ensures the code runs only once the document is fully loaded. The code snippets you see below all assume they’re running inside this function, so if you encounter errors, remember to check their placement:

$(document).ready(function(){    // everything goes here});

Since the Sprite menu is our fallback state when JavaScript is disabled (if we’ve done it right), we’d better get rid of those CSS-applied background images on hover, because we’ll create our own in the script below:

$(".nav").children("li").each(function() {
    $(this).children("a").css({backgroundImage:"none"});
});    

On the first line, we’re querying any elements with a class of nav and attaching a new function to each child li element they contain. That function is on the second line, and it queries the this object for any a child elements. If it finds them, it sets the CSS background-image property to none. Given the context, this means the li elements that the function is running on.

Example 2: Disabling CSS hovers with jQuery.

That works…but we also lose our currently-selected item in the process. So we need to throw in a check to see which item we’ve identified with the current-(whatever) class we applied to the parent ul, and skip that one from our background image removal. The previous snippet needs to be expanded a bit:

$(".nav").children("li").each(function() {
    var current = "nav current-" + ($(this).attr("class"));
    var parentClass = $(".nav").attr("class");
    if (parentClass != current) {
        $(this).children("a").css({backgroundImage:"none"});
    }
});    

The second line now creates a variable called current that uses each li’s class in sequence to create a string that should match the parent ul’s classes, if that particular li is the currently-selected item. The third line creates a second variable which reads the actual value directly from the ul. Finally, the fourth line compares the two. If they don’t match, only then do we set the background-image property of the a element. This skips the change in background image for the currently-selected item, which is exactly what we’re shooting for.

Attaching the events#section4

Now we need to attach a function to each of the li elements for every interaction event we want to style. Let’s create a function for this, called attachNavEvents:

function attachNavEvents(parent, myClass) {
    $(parent + " ." + myClass).mouseover(function() {
        // do things here
    }).mouseout(function() {
        // do things here
    }).mousedown(function() {
        // do things here
    }).mouseup(function() {
        // do things here
    });
}

This function takes two arguments. The first is a string containing the literal class of the parent element, complete with preceding period, as you’ll see when we call it below. The second is a string containing the class of the particular li to which we’re attaching the events. We’re going to combine both of those on the first line of the function to create a jQuery selector that targets the same element as the CSS descendant selector of, for example, .nav .home. (Which element depends on the arguments we’ve passed to the function, of course.)

Because jQuery allows us to chain multiple functions on a single object, we’re able to create all the event-triggered functions at the same time. Chaining is a unique jQuery concept. It’s a bit tricky to wrap your mind around—it’s not essential to understand why this works, so if you’re confused, just take it for granted that it does work, for now.

Now we’ll attach these functions to every item in our navigation. The following is a verbose way—we’ll optimize it later—but for now, let’s run the function on every li. For arguments, we’ll pass the parent of each, as well as the li’s own class:

attachNavEvents(".nav", "home");
attachNavEvents(".nav", "about");
attachNavEvents(".nav", "services");
attachNavEvents(".nav", "contact");

This doesn’t do much yet, but we’re about to fix that.

Example 3: Basic script setup for events.

The theory#section5

I’m going to explain what happens next up front. Stay with me—it’s important to understand what’s going on here because you’ll need to style the elements we’re manipulating.

For each of the links, we’ll create a brand new div element inside the li we’re targeting, which we’ll use for our jQuery effects. We’ll apply the nav image to this new div using the same background-image rule we used for the a element inside the shared parent li. We’ll also absolutely position the div within the parent. It’s more or less a duplicate of the existing a element in our CSS Sprites setup. Through trial and error, I’ve found that this new div creation proves less glitchy than directly applying the jQuery effect to the existing elements—so it’s a necessary step.

The style for this div must already exist in the CSS. We’ll create a new class for this li (.nav-home), based on the class of the targeted li (so it doesn’t conflict with anything else we’ve created so far), and add the style:

.nav-home {
    position: absolute;
    top: 0;
    left: 23px;
    width: 76px;
    height: 48px;
    background: url(../i/blue-nav.gif) no-repeat -23px -49px;
}

The practice#section6

Now it’s time to add the effects. When the mouseover event is triggered, we’ll create the div element and give it the previously-mentioned class. We need it to start out invisible before fading in, so we’ll use the jQuery css function to set a CSS display value of none. Finally we’ll use the jQuery fadeIn function to fade it from hidden to visible, and pass an argument of 200 to that function to specify the duration of this animation in milliseconds (line wraps marked » —Ed.):

function attachNavEvents(parent, myClass) {
    $(parent + " ." + myClass).mouseover(function() {
        $(this).before('');
        $("div.nav-" + myClass).css({display:"none"}) »
        .fadeIn(200);
    });
}

Then, we perform the same in reverse on the mouseout event—we’ll fade out the div. Once it’s finished fading, we’ll clean up after ourselves by removing it from the DOM. This is how our attachNavEvents function should look:

function attachNavEvents(parent, myClass) {
    $(parent + " ." + myClass).mouseover(function() {
        $(this).before('');
        $("div.nav-" + myClass).css({display:"none"}) »
        .fadeIn(200);
    }).mouseout(function() {
        // fade out & destroy pseudo-link
        $("div.nav-" + myClass).fadeOut(200, function() {
            $(this).remove();
        });
    });
}

And that’s pretty much it for the hovers:

Example 4: Scripted hover events.

We’d better do something about the mousedown and mouseup events too, if we had previously defined a change for the :active state in the CSS. We’ll need a different class from the hovers so we can target it uniquely in the CSS, so let’s change the class on mousedown. We’ll also want to revert it on mouseup to restore the :hover state, since the user may not have moved their mouse away from the nav item. Here’s what the revised attachNavEvents function now looks like:

function attachNavEvents(parent, myClass) {
    $(parent + " ." + myClass).mouseover(function() {
        $(this).before('');
        $("div.nav-" + myClass).css({display:"none"})»
        .fadeIn(200);
    }).mouseout(function() {
        $("div.nav-" + myClass).fadeOut(200, function() {
            $(this).remove();
        });
    }).mousedown(function() {
        $("div.nav-" + myClass).attr("class", "nav-" »
        + myClass + "-click");
    }).mouseup(function() {
        $("div.nav-" + myClass + "-click").attr("class", »
        "nav-" + myClass);
    });
}

We can reuse the hover div style, by slightly modifying the background position to adjust which part of our main Sprite image is showing on click:

.nav-home, .nav-home-click {
    position: absolute;
    top: 0;
    left: 23px;    width: 76px;
    height: 48px;
    background: url(../i/blue-nav.gif) no-repeat -23px -49px;
}
.nav-home-click {
    background: url(../i/blue-nav.gif) no-repeat -23px -98px;
}

Now we’ve got the hovers, the currently-selected nav item, and click events all worked out:

Example 5: Putting it all together.

Other considerations#section7

We’re not limited to the fade effect either. jQuery has a built-in slideUp/slideDown function we can use as well (which is shown in the second example at the top of this article). Or, we can get really fancy and create custom CSS-defined animation effects using the jQuery animate function (as shown in the third example). A word of caution about animate—the results can be a touch erratic, as you may have noticed in the example.

Cross-browser functionality is a bit of a freebie; jQuery works across most modern browsers, so everything you see here works in IE6+, Firefox, Safari, Opera, etc. We’ve also accounted for multiple graceful degradation scenarios. If a user has JavaScript turned off, they get basic CSS Sprites. If they’ve disabled JavaScript and CSS, they get a basic HTML list. And, we get the other benefits of CSS Sprites too, since we’re still using a single image for all the various navigation states and effects.

Though it’s not required, it’s strongly suggested that you embrace subtlety; animation speeds of more than a few hundred milliseconds may be fun to begin with, but they will quickly grate on the nerves of those who use the site you’re building after the novelty wears off. Err on the side of quicker animation speeds, rather than slower.

One potential glitch you might run into is when other text on the page seemingly “flashes” during animations. This is a complicated issue that has to do with sub-pixel rendering common in modern operating systems, and the best fix seems to be to apply a just-slightly-less-than-opaque opacity value to force a particular text rendering mode. If you add this to your CSS, the flashing should clear up at the expense of regular anti-aliased text instead of sub-pixel anti-aliased text:

p {
    opacity 0.9999;
}

In the demos this is applied to p, but that caused a conflict with the A List Apart CMS. But we can apply this rule to any element on the page, so let’s pick something innocuous.

Packaged to go#section8

You don’t actually need to remember any script in this article, since there’s a pre-built function awaiting you in this final example. Using the JavaScript in the HTML file as a reference, you only need to edit a single line of JavaScript to apply Sprites2 to your site:

$(document).ready(function(){
    generateSprites(".nav", "current-", true, 150, "slide");
});

The generateSprites function takes five arguments:

  1. The primary class of your parent ul, including the period.
  2. The prefix you’re using for selected items, e.g., for a selected class of selected-about, use selected- as the value.
  3. A toggle to indicate whether you’re styling the :active state. Set it to true if you’ve defined the :active state and jQuery equivalents in your CSS, otherwise set it to false.
  4. The animation speed, in milliseconds. e.g., 300 = 0.3 seconds.
  5. Your preferred animation style, as a string. Set to “slide” or “fade”, it defaults to the latter.

Example 6: One easy line of script to modify, thanks to the pre-built function.

You will still need to position and style the various scripted elements in your CSS, so feel free to use the CSS file examples in this article as a reference.

Footnotes#section9

During the writing of this article, a similar technique was written up elsewhere, albeit without our nice CSS Sprites fallback. We also discovered a very different animated jQuery menu that you may find useful.

86 Reader Comments

  1. Apologies for the tangential comment, but with jQuery’s support for cross-browser CSS3 selectors, effects library, and other javascript things like variables and so on, how long is it until we’re writing our CSS in jQuery?

    I know it seems like a terrible idea at first glance, and there are obvious caveats (speed – though John Resig is testing a new, fast selector engine, JS support, complexity, verbosity etc) but then again.. Variables! Conditionals! Effects!

    It does open plenty of doors that spec creators/browser developers aren’t going to be opening any time soon which web designers/devs have been kvetching about for a while – food for thought anyway 🙂

    Anyway, it looks like jQuery (or your framework of choice) techniques are going to be a standard part of the modern web designers arsenal, so it’s nice to see them applied to some of the ‘old faithful’ CSS techniques 🙂

  2. I love the idea of sprites. I really think that using flash to animate things is ineffective in it’s intent, and I don’t think it’s js counter part is much better. I think the reason why flash is not cool is because of the animation. I think others would agree as well.

  3. In the section “Initializing in jQuery” you use the code

    $(“.nav”).children(“li”).each(function() {
    $(this).children(“a”).css({backgroundImage:”none”});
    });

    This could be done more efficiently (and more straight forward if you ask me) with the following:

    $(“.nav li a”).css({backgroundImage:”none”});

    I may be missing something but the anonymous function to get the second array of children a tags seems a bit much…

    Otherwise, awesome article! Thanks, Dave!

  4. I’ve been using jQuery on every project for about six months now. I’m always surprised by how quickly it renders and how well in runs in IE6. It can even handle some very complex page elements that are typically reserved for Flash. Here’s an example of I put together of IGN’s Flash slideshow, built with jQuery, as an example of its durability. “IGN Flash Slideshow in jQuery”:http://www.setsolid.net/ign

  5. @Nic Johnson – while you’re certainly correct in pointing out that particular snippet seems like it could be written more efficiently, you’re also optimizing too soon. Take a look below at the next code block, you’ll see that the earlier example has been expanded upon. The reason for the more verbose code is that a conditional is later required to ignore the currently-selected state.

    While we did try to optimize the code as best as possible before publishing, it’s entirely possible there are still improvements that can be made.

  6. I am so glad jQuery is becoming more and more used as a flash alternative that I may shed a tear of joy. I’ve been using it for just a few weeks now, but I can already see its power and ease of use in many situations where I would otherwise struggle with complex javascript at the point of giving up. Anyway, thanks for the nice article Mr. Dave.

  7. Is this method actually *needed* (and I am not meaning _critical_ for UX)? And what is this technique’s *impact on performance* (load time, especially if inexperienced users don’t already use jQuery in their projects)?

    No need to answer I guess.

  8. When hovering over the items in the third example I see the items shortly stick out at the bottom in a flash which looks a bit weird. Browser: FF3 Mac OS X.

    Small unrelated side note: The submit button for the comment form on ALA cannot be clicked in FF3 and in safari you can ONLY click the 1px white inner border. You may want to fix that.

  9. Is overriding the paragraph opacity necessary? The lack of sub-pixel text rendering is far from pretty. It renders rather jaggy here in both Firefox and Safari. Unacceptably bad.

    I saved a copy of the article page and added “p { opacity: 1;}” to the stylesheet to override the 0.9999 to see this bug for myself. I tested in Firefox 3.0 and Safari 3.1.2 on Mac OS 10.5.4 and couldn’t reproduce this.

    I also noticed the referenced mezzoblue article is from 2006. Rather old in the non-IE world. If anyone can reproduce this and thinks any flickering is worse than bad font rendering, please comment.

    If this bug does still exist in commonly used browsers, perhaps a bit of runtime JS to workaround those instances is in order rather than a blanket feature downgrade.

    P.S. My browsers are also affected by the submit button bug mentioned by Marco. I managed to activate it by tabbing and pressing enter.

  10. Javascript as a programming language has been treated like no other. The overall lack of understanding has quickly resulted in enforcing the “cut and paste”? style of programming where you don’t necessarily want to know how and why something works, just as long as it does. It has also resulted in the assumption that one could, for example, use the coding styles learned from CSS or PHP etc. in Javascript. Usually we’d bother to learn a language (and learn it deep, like Douglas Crockford has been saying for years) before using it, but that hasn’t been the case here and it has set the language back quite a lot.

    That said, there’s nothing wrong with using jQuery as a gateway into the language. It does, however, put enormous pressure on the gatekeepers who make the tutorials and who dish out “best practices”. Again, I’m not criticizing _this_ article, at all, but it should be noted that a generation of mindless Javascript “programmers” will lead to problems, and in my opinion fixing those problems will make the transition from tables to CSS seem like a walk in the park.

    (oh, and your submit button is broken in Safari – there’s a clickable area with a height of only 1px )

  11. Concerning the eas-up that Nic Johnson suggested I admit, Dave Shea, that he optimized “too soon”. However I think also your second snippet can be done more efficient and especially more maintainable. I would transform it into:

    var current = $(“.nav”).attr(“class”).replace(/.*current-([^ ]+).*/, “$1”);
    $(“.nav li:not(.”+ current +”) a”).css({backgroundImage:”none”});

    Most notably due to the regular expression I use this will work even if your ul.nav has additional classNames. Plus this is a great example for the power of jQuery’s CSS-selectors!

    Anyways I really do like your unobstrusive way of creating menus with smooth effects.

  12. Oh, I see that textile messed up thes javascript I just posted. Each dot in the regular expression (inside the two slashes) is followed by an asterisk * (which textile turned into *bold* text). The variable called current is then inserted into the jQuery selector using two plus-signs + on each side (which textile turned into +underlined+ text).

  13. bq. When hovering over the items in the third example I see the items shortly stick out at the bottom in a flash which looks a bit weird. Browser: FF3 Mac OS X.

    Yes, when all three examples are viewed together, as they are at the top of the page, there is a brief extra hover at the bottom, just as you describe. But this only occurs at the top, where all three examples are viewed together. The actual examples work fine individually — and that’s how they’d be used.

    bq. Small unrelated side note: The submit button for the comment form on ALA cannot be clicked in FF3 and in safari you can ONLY click the 1px white inner border. You may want to fix that.

    Thanks for calling that to our attention. *It happens only on this article*, and it appears to be caused by an unforeseen interaction between our code and Dave’s. We’re looking into it now.

  14. Thanks for a great article Dave. I’m a long time fan.

    Just thought I’d point out a small bug. Mouse-down on an item changes it to the active state. If you then drag outside the item, it stays active. In other words, a click-and-drag on an item makes it stay in the active state.

    I made a quick fix that seems to work. I just duplicated the line from the mouseup handler to the first line of the mouseout handler. So the mouseout handler looks like this:

    mouseout(function() {
    $(“div.nav-” + myClass + “-click”).attr(“class”, “nav-” + myClass);
    $(“div.nav-” + myClass).fadeOut(200, function() {
    $(this).remove();
    });
    })

  15. Small suggestion for improvement: If you click a button and release outside of the navigation, the button will keep it’s pressed state. I now it’s a little nitpick but still: it feels weird.

    Apart from that I really enjoyed the article! Well written, easy to understand and a beautifully designed example. That’s what I love ALA for.

  16. My example above looks broken. Anyway, all I did was to add this line to the beginning of the mouseout handler.

    $(“div.nav-” + myClass + “-click”).attr(“class”, “nav-” + myClass);

    (How should code blocks be posted? All white-space is mangled and it interferes with Textile.)

    @Reimar Servas: Hivemind! =D

  17. Is the opacity change on P tags really necessary? In Firefox 3 with Cleartype enabled the text is beginning to strain my eyes.

    I also notice that the background images sometimes shift ‘out of bounds’ as they begin to animate.

  18. My question would be: what does this add? A visually nice gimmick, but with no added value. Pure CSS or the heavier, kinda exclusive JQuery version? Feels weired to me adding KB and markup to a page without improving, say usability.

  19. Call me a silly billy, but wouldn’t this be achievable with no jQuery using animated GIFs as background images to the link? (Dave knows my fondness for the animated GIF.)

  20. Call me a silly billy, but wouldn’t this be achievable with no jQuery using animated GIFs as background images to the link? (Dave knows my fondness for the animated GIF.)

  21. Thanks fo a very detailed practical quality article!

    I haven’t looked at it in detail yet, but I know from experience (implementing a fade in/out on text links for the nav on my site) that making the fades behave nicely can be a little bit of a headache (they tend to keep on fading in/out long after your mouse is gone if you pass over a few times in a row…).

    I’m surprised some people would complain about the fact that it “doesn’t add anything” in terms of usability and therefor dismiss the whole thing as pointless. This technic doesn’t “remove” anything either… and it sure looks nice… If you think it’s pointless, you must not listen to your clients very carefully… 😉

    Thanks Dave, I’ll make sure to go over the details this weekend and see how you’ve implemented it 🙂

  22. Being a long term jQuery user I’d go all the way and make the generateSprites function a jQuery plugin. Would look like the following:

    $(‘ul.nav’).sprites(‘current-‘, true, 150, ‘slide’);

  23. I just remembered all the confusion and hatred that CSS Animations have caused a couple of months ago when they were proposed by Apple.

    And here – a whole A List Apart article at length explaining how to implement in a lot of non-semantic Javascript code something, that is a purely design decision and could be done with the above-mentioned technologies in two lines. *Sigh*!

  24. The articles states, “The most compact version of jQuery weighs in at 15k.”
    But this is only for the minified, g-zipped version which may not be a viable choice for many. Plus the time to decompress must be taken into account.
    jQuery’s full size is 97kb.
    And in practice, there is little difference between the full version; the minified and g-zipped version; and the packed version. (The last choice created with Dean Edwards Packer)
    In all but the full version there is overhead related to decompression.

    The creator of jQuery, John Ressig, thoroughly discusses the the trade-offs here:
    “http://ejohn.org/blog/library-loading-speed/”:url
    Also, a recent study by Yahoo surprisingly found that 40-60% of Yahoo!’s users visit with an empty cache.
    “http://yuiblog.com/blog/2007/01/04/performance-research-part-2/”:url
    Ressig addresses this issue as well:
    “…according to Yahoo’s research on caching, approximately 50% of users will never have the opportunity to have the page contents be cached. Thus, making sure that your page loads quickly both on initial load, and subsequent loads, should be of the utmost importance.”

    The article understandably puts the best face possible on the download costs but be aware.

  25. In case anyone cares, I re-wrote the JS as a Lowpro behavior class. It requires Prototype, Lowpro and the Scriptaculous Effects file be included first. It’s here if you want it:

    http://farrellit.com/js/navsprites.js

    You activate it like this:

    Event.addBehavior({
    ‘.nav’: NavSprites(‘nav’, ‘current-‘, 150, ‘slide’)
    });

    See it in action here:

    http://farrellit.com/testing/

    Main advantage is that it’s a single event listener using event delegation. I also have a Jquery/Lowpro version but that is a pretty niche group.

  26. Not to go over this again, but your initial jQuery could be redone in a much cleaner way — sans regex. The current section designation should be attached to the appropriate element as the “current” class, not as an odd hyphenated combination in the parent class. Also, if the page is built dynamically the list will be made in a loop and the class assignment can happen much easier there.

    Assuming the “current” class is attached to the list elements, not the parent list:

    $(‘.nav’).children(‘li’).each(function() {
        if (!$(this).hasClass(‘current’)) {
            $(this).children(“a”).css({backgroundImage:’none’});
        }
    });

  27. Sorry for the non-breaking muddles in my post above. Proper code:

    $(‘.nav’).children(‘li’).each(function() {
    if (!$(this).hasClass(‘current’)) {
    $(this).children(“a”).css({backgroundImage:’none’});
    }
    });

    Also, the reason the submit button can’t be clicked is because the syndication paragraph is jumping over top of it.

  28. I’m going to try and address some of the questions and comments that popped up overnight.

    First, the ALA submit button — as Jeffrey mentioned, we’re on it. I think we’ve got a fix worked out, stay tuned.

    @Jens Meirt, @Florian Schmitt, and anyone else wondering why this might be needed – let’s keep in mind that modern operating systems have been adding more and more simple UI animation effects for ages. Think of the way a hidden Apple dock will slide in, or the way way Exposé quickly moves and scales windows, or window minimization effects, and so on. These are little UI details that may not strictly be necessary, but the transitions aid a user’s ability to keep track of what happens one minute to the next when the screen quickly changes, where a non-animated immediate transition could be quite confusing. Can we lump animated navigation hovers into the same category? That’s probably a subjective decision. I’d argue that if we’re going to bother defining :hover states at all, it’s nice to have the option to do something more than a binary on/off transition. Hence, this article.

    @Marco van Hylckama Vlieg, and anyone else noticing the glitchy third example in the article – yeah, this one certainly wasn’t perfect. I acknowledged later in the article that jQuery’s animate function causes a bit of randomness in that example (see “Other Considerations” heading). I’m now wondering if it might be possible to hide the overlap with a bit of overflow:hidden magic; didn’t think of it at the time, but it might be worth digging in a bit more.

    @Jason W, @Rowan W, and anyone else concerned about the opacity: 0.9999 addition – the problem with that NOT being there is that on hover, most browsers will cause the rest of the text in the page to flash in between anti-aliased and sub-pixel anti-aliased text during the animation. I’m pretty sure this is a browser bug, and if I understand the nature of it, it’s not an easy one to solve. The fix is sort of a lesser of two evils choice, but for now it’s the best we’ve got.

    @David Tapper, @Reimar Servas, and anyone else concerned about the click+hold+move the cursor out of the link glitch – yep, we were aware of this one too and I had a last-minute addition to the article that doesn’t seem to have made it in explaining it. This behaviour is actually similar to how Firefox treats active links when you move the cursor out of a clicked link, believe it or not. The difference is that you can click elsewhere in the page to de-activate the regular link, whereas with Sprites 2 you have to click the link again. So, not ideal, and a bug, and thanks for your suggested fix David Tapper!

  29. @Christoph Tavan, @Michael Thompson, and anyone else suggesting further optimizations for examples 2 through 4 – I haven’t looked closely at your suggestions yet, but just I’ll point out that the early examples are building up to the later examples, and the final code is what we’re more interested in optimizing. Example 2 leads into example 3, so writing the first example without keeping in mind the conditionals we’ll need in the next example (or later), then having to explain each time why we changed the earlier code would make the prose a lot more clunky. Your suggestions may avoid this of course, so ignore me if I’m just stating the obvious again.

    @Bruce Lawson – I’m not sure if you’ll believe me, but I tried exactly that about 4 years ago and found out then that browsers will only play endlessly looping animated GIFs as expected. For one-time animations that stop after a few frames, they’ll play the animation when the image loads and then stop, without re-triggering the animation on each mouseover. So, good idea, but foiled by the browsers once again!

    @Jakub Nesetril – I don’t think I can disagree with you on that sentiment.

    @Richard Fink – Thanks for the links, I didn’t have that data available when writing. Decompression is certainly another factor to keep in mind.

    @Daniel Farrell – good stuff!

  30. … you’ve got an honest face.

    Does sound like a browser bug, I agree. Will test it in Opera and add it to the bug tracker (after looking at some godforesaken ancient document that specifies animated GIFs to find out if it is actually a bug.)

  31. in example 1, you mention that you have included :focus – making it nice for keyboard users. subsequently, you quickly take it away again via jquery from example 2 onwards.

  32. To those that argue this unnecessary could easily argue that of a number of things when it comes to design. Is a gradient necessary? Are rounded corners necessary? Why not just give it a background colour and be done with it, Dave?! Think of the bandwidth of having to download all those extra images! I throw my arms up in disgust at the wasted pixels…

    Or we could just marvel at how pretty it is.

  33. As Snook pointed out, not everything in a web-site is purely created for improved usability. This example adds some nice aesthetic functionality that is only meant to please the viewer. That has the same goal as putting in gradiants and illustrations.

    An impressed viewer is more likely to accomplish your site goals, because they enjoy using it, and will thus continue to the end.

    Plus, this degrades nicely, so there’s not much loss, the only downside could be the filesize, and in that case, I’d say whether or not to use this would depend on your target. Aim your web-site at consumers with modern technology, and this makes it pretty.

  34. Dave, really great article. One minor nit – when I clicked your link “Google is now hosting jQuery” [ http://code.google.com/p/jqueryjs/ ], I expected it to go to http://code.google.com/apis/ajaxlibs/documentation/index.html#jquery which contains the CDN url for jQuery. This would be the recommended one if people are hoping users would have a cached copy as another site may have already referenced it. It’s correct in your example code, but not obvious from the link you’ve given (which is to the google code project page).

  35. Love the article…

    Question though. I’m working on a project that I would like to use this functionality on. My original design has a main nav menu similar to the one in the example, with 4 links. These have a submenu of links that appear on mouseover of the main nav links. I’ve been able to successfully add the functionality in this article, but when you move the cursor into the submenu links, you obviously lose the hover state on the main menu, thus creating some minor confusion on which set of submenu links you are viewing.

    Is it possible to call the hover effect into focus using a mouseover call from somewhere else on the page? I’m new to jQuery and can’t quite figure out how to accomplish this. Thanks for any help and I hope I explained things thoroughly.

  36. Nevermind…I think I got it figured out. I basically copied the attachNavEvents function and assigned different ids, and then referenced the ids of the main nav section in the new functions. Fun stuff.

  37. Hi guys,

    How does this method affect the optimization of the page on the search engine (Google in particular)?

    I know that if you disable the css, you will still see all the links on the menu, however on the css the link itself has a text-indent:-9000px;

    In Google Quality Guidelines, it states to avoid hidden text or hidden links.
    http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=35769#design

    Your site may be removed from the Google index, and will not appear in search results pages.
    http://www.google.com/support/webmasters/bin/answer.py?answer=66353

    Any feedback appreciated …

    Cheers, 🙂

  38. This is cute technique and an interesting article, but does nobody write without jquery [et al] anymore? I’d be far more interested in seeing actual code to do this, not meta-code.

  39. I’ve never been a big van of javascript “frameworks” in general, since they seem an extra layer of abstraction on an already simple scripting language.

    For those unwilling to snuffle through script, I have something similar that can be applied through a class-name in my classBehaviours collection: http://www.classbehaviours.com/details.php?id=cosmetic-mouse-interaction

    I didn’t take the “sprite” approach though, my “animated class-name” example could be adapted for that.

  40. @ *Joy Budiman*: Here is a link you should find helpful. It was posted on Google’s Webmaster Help forum and linked too from Google’s Webmaster Blog a few days ago. “Google’s Clarification on Hidden Text”:http://groups.google.com/group/Google_Webmaster_Help-Indexing/msg/d6aa4da7a87e5230

    @*Maurice van Creij*: The major advantage of jQuery, for me, is that it’s pre-tested to work in all of the popular browsers. I personally don’t know enough about Javascript to know how to fix all of the cross-browser problems that may occur, however, my clients are requesting more and more Ajax from me (a front-end developer). jQuery gives me fast turn-around and allows me to keep my attention on other things, while keeping me from having to outsource parts of my work to a Javascript expert.

  41. I was trying to do something similar recently but which added an additional hurdle: using transparent PNGs. I finally figured out a way to get them to display properly as background images in IE6, only to find out that you can’t change their background position, rendering the whole concept of CSS sprites useless.

    Any ideas on how to get around this? Could the same method work with individual images set as background images for each list item?

  42. It’s worth emphasizing that this technique degrades gracefully; if you disable Javascript, the mouseover effect still works, albeit without animation.

  43. I have a hard time figuring out the benefits of the CSS technique used to define the current element. Using a current class on the li would greatly simplify the JS and the CSS a bit too.

    Is there a particular reason for this structure or is it an IE 5 inheritance ?

  44. CSS Sprites is a nice yet extremely hard to manage menu effect. But this is not CSS Sprites2. I’ve played with this method before, and it has little relation to CSS Sprites. Seems a little audacious to call it that.

    That said, jQuery rocks, and I’m always glad to see it get some love! 🙂

  45. Great technique, useful especially when you need jQuery for other tasks too.

    I modified the script a little to suit my needs adding focus() and blur() events to make it more usable for keyboard navigation. That was the only thing I was missing in the example

  46. Took a look at your article and decided to go ahead and make it happen. With some tweaking of the ‘sprite’ I managed to get the same image to function as both the unfocussed and focussed tab, and the various images that sit on top of it. You can see it here:
    http://www.partyark.co.uk

    Thanks for the inspiration!

  47. Perhaps somewhat off topic, but why is it that so many love to quote Jeremy Keith and unobtrusive scripting, while at the same time missing the boat by ignoring the principles of graceful degradation or “Hijax”?
    From an accessibilty perspective, what’s the difference between refering to a plain “#” or using inline javascript in navigations? While in the past, it might have been a bit of a struggle to hijax URLs, but the “preventDefault” method in jQuery (and MooTools …) has made it so easy. Can’t we just use it?

  48. I personally don’t find the one that changes colors particularly useful, and I find the animations annoying. If anything, this slows me down, not a good thing for the web.

    For this to be Section 508 accessible, there needs to be a way to turn the animations off.

  49. This is a bit of an aside, but what happens when images are turned off. As far as I can tell the whole menu disappears. Are we assuming with the css sprites that users who disable images will also disable css.

    You mentioned in your original article that there was no known way around this. Has anything changed? And do you see this as an issue?

  50. Great article, Dave. I might even read it again just to pound it into memory. Although I noticed one slight bug: your :focus lacks the removal of a border. If you click (and hold) on an element in Firefox 3.0.1 (not sure of others) you see a read border around the element, and when released — as the focus still holds — a blue border is shown. Obviously a bit tacky for the perfectionists in the group.

    Great article either way, perhaps soon these sorts of tricks can just remove the useless flash objects from the web, and only keep the truly unique ones.

  51. Thanks for the tutorial, great effect… however, it appears broken in IE6. Has anybody gotten this to work in that browser?

  52. Interesting technique! I have been toying with it for a while, and am now using it in the website I’m currently working on. However, I didn’t want to rely upon a large JavaScript framework, so I actually wrote my own custom animation code. It condenses to not much more than 3K. (Just over 4K uncondensed.) I’ve went some way into abstracting it and adding some more advanced functionality, and it doesn’t look like it will go over 8K uncondensed; it’s currently at about 4.5K. If I can make it easier to drop into place—the trade-off for conciseness is complexity, in this case—would anyone be interested?

  53. Not sure if it’s your script or CSS, Dave, or if it’s just the ALA’s lack of support, but keyboard support should be added. I can’t of a reason why it would be left out.

    @Bruce Lawson, re: animated GIFs. I tried that (“see experiment here”:http://green-beast.com/experiments/css_flashy_links.php) and it works nicely (don’t use a looped animation solves the looping problem), but only in Firefox. It seems to behave differently in all browsers, thus Dave’s solution is better (albeit lacking in accessibility).

  54. I am using this technique with transparent png’s as background images. It works fine in FF, Safari and Opera, but not in IE7.

    On the hover state, and just for a moment, a black background fills the area that should be transparent, and then suddenly it gets transparent as it should. It looks as if the browser was too slow rendering the transparency.

    Any idea on how to fix that?

    Thanks.

  55. This is really an interesting technique. And probably there are also some other fields except from the navigation where you can use the sliding background. You only have to be creative!

  56. I think this article is quite lacking. We “all” know about CSS sprites by now and this cut and paste jQuery fluff just isn’t enough to be a whole article? I think you should have made this whole article a “Part I” and then expanded on the concept adding sub navigation, sub-sub.. navigation.

    You should and also have made some real-world examples where this actually would improve anything that we have not yet seen that much of – this “we” see every day and it just isn’t worthy of an ALA-article? …

    Good instructions for newbies though.

  57. Terribly sorry to be posting on the CSS Sprites2 thread, but the comments link on the original Sprites page seems to be broken. I’m working on a vertical menu application for the original Sprites, but I’m having some problems translating the code — specifically I’m having trouble assigning a larger height to a taller link. The link in question is the “Get Tapped In” link on the left side here: http://www.woodntap.com/new/

    This is the CSS: http://www.woodntap.com/new/css/wnt_home.css

    I’m sure I’m just overlooking something fairly obvious — any ideas?

  58. @Brian Lee

    The link elements are only as high as the text which they contain. You need to give the anchor a bit more height:

    bq. #tapin a
    {
            height: 75px;
    }

  59. Well that didn’t exactly render how it showed in the preview.

    How is one suppose to quote code with this Textile thing anyway? (Markdown support and an accurate preview would be nice).

  60. I rewrote CSS Sprites2 in Prototype and script.aculo.us. My version of CSS Sprites2 is almost exactly the same as the Dave’s: it requires the same HTML, the same styling and includes a similar pre-built function used to invoke it. However, the Prototype version also includes keyboard support: there’s a matching focus, blur, keydown and keyup event handler for each mouse event handler.

    You can get it here: http://jeffreybarke.net/2008/11/css-sprites2/

  61. This script does not work for the effects advertised in this site. It will work for just a basic background swithout.
    If you are just looking for a basic rollover, it will work.
    However, there are much simpler scripts out there for his .
    The sprites2.js DOES NOT WORK.
    Tested in FF3, IE6 and 7 (who cares about Safari)

    Glad i could save someone a few minutes!! 🙁

  62. External scripts are usually used by advertisers. Personally, I have them disabled. and so do other people. For example the confused people in previous comments that claim the example script doesn’t work. caching isn’t everything, and it’s not worth the security risk you put your users in. Google APIs or not, you can’t trust them. or at least your users wont.

  63. The examples embedded in the article itself don’t seem to be working for me in IE6. I’m on a mac and using VMware Fusion to test in IE6 — perhaps that’s the problem? Works in FF and IE7 in Fusion, but in IE6/Fusion I have to hover over a button for about 7 seconds and then it finally just switches to the rollover state. I don’t have a resource, at the moment, to test in IE6 without Fusion. Any clues anyone?

  64. I have been a hold out – using unobtrusive javascript rollovers instead of sprites. I finally tried it out and must say that sprites were easy to implement, made the development process faster since I had fewer images to slice and I really liked using the fade in for the menu tabs on my website.

  65. One glitch I’ve seen that is driving me crazy, and it only happens for my Mac in my Firefox, not in my Safari and not for my PC friend in Internet Explorer…

    When I hover over a button, then hold the cursor there for a second or two, and move it WITHIN the the sprite refreshes. I don’t even move it outside of the button, any movement within the button after a delay refreshes the image and causes a new fade in. Its similar to a flicker or a flash, and completely ruins the continuity of the menu.

    Any ideas what this is and how to solve this?

  66. I found a machine with IE6, to test on, and it works! so, i guess it’s just an issue with testing in IE6 on my mac, using a virtual machine / VMware Fusion. Thanks!

  67. Great article, I may say. I’ve tried to attach this effect on my WordPress blog these two days but with no success. No matter how much I trie I can’t get it to work with the built-in list command (wp_list_pages) in WordPress. Does anyone have any suggestions on how to make this work with WordPress?

    TIA
    Daniel

  68. Hi. I don’t like absolutely positioned navigation elements. They’re useless for practical purposes.

    I am trying to get this example working with the following HTML:

    <ul id=“nav”>
      <li>Articles</li>
      <li>News</li>
      <li>Contact</li>

    My CSS is like this:

    /* Nav */
    ul#nav { padding: 0; display: block; float: right; margin: 5% 20% 0 0}
    ul#nav li { background: transparent; margin:0; padding:0; height: 20px; list-style:none; width: 130px; display: block; border: 0 }
    ul#nav li a { background: url(/av/nav.png) no-repeat; width: 130px; display: block; height: 16px; text-align: left; text-indent: -10000%; margin:0; overflow: hidden }
    
    a#arti { background-position: 0 0; }
    a#arti:hover, a#arti:active { background-position: -129px 0px;}
    li.curr a#arti, li.curr a#arti:hover { background-position: -258px 0px; }
    

    ..and so on. Basically the code is NOT working. What do I need to do to make sure non-absolute items work too?

  69. Whats with the textile drivel here? A website about web design cannot use a more modern system for commenting that allows us to post some simple code?

  70. For so much effort as to include a JS file and whatnot, this simply does not work.

    (1) In Firefox in Mac (which is quite a prevalent environment now) it flickers forever.

    (2) Doesn’t work consistently in IE7. Sometimes the animation doesn’t happen at all, and other times happens jerkily.

    (3) This whole shebang doesn’t work with a vertical sprite at all. Silly shite.

    Sorry. I appreciate the effort on the part of the writer, but even with loads of tweaking, this drivel simply does not work with vertical sprites because of the DIV that is appended to the css links. That may work for horizontal sprites, but in a vertical menu it is all over the place.

  71. This menu is beautiful and is a wonderful tutorial for those new to jquery and manipulating css. Thanks very much for the post, I have benefitted from this tremendously!

  72. Try as I might, I can’t get this to work. I’m seeing nothing on rollover (in FF3 Mac) and then, on rollout, it switches to the rollover state for a few milliseconds and then fades out. In Safari, when I rollover, it flickers and tries to run the fade in/fade out over each pixel I move the mouse. When I look in Firebug in FF3, rollout causes a slew of classes to be created and then they slowly remove themselves. In Safari, moving the mouse in rollover causes the same thing. Active state works fine, though.

    I thought it might be an issue with the newer version of jQuery, but I switched out 1.3.2 for 1.2.6 and still had the same issues.

    I have pretty much no experience with JS, so I’m at a loss to troubleshoot it. Any ideas?

  73. I’m also having issues with my buttons flickering in FF3 for the Mac and one of my associates sees it in FF3 for Windows. However, when I check the page showing the final example in your article in the same browsers the rollovers work beautifully. I’m using a similar structure in my menu and the same js file, so the only thing I can think that would be different is the CSS. I’d love to figure out a fix for this because it’s a great effect.

  74. Thank you for the great article.

    By removing onmousedown- and onmouseup-events though I’ll reduce the size of the js and css in half, and still have awesome results.

  75. Hi There,
    Thanks for a great insight into jquery sprites!!
    I have tried to apply this tut to a menu I am working on and it seems to work fine apart from the selected state.

    I can only get a selected state to work for the second li item??

    I change the

  76. I’ve been playing with this using transparent PNGs for the menu, and it is not working correctly….I think.

    When the page loads, I see is the menu starting position rendered correctly. However, the rollover events are not correct, because both the normal state and the hover state are being shown.

    I believe this is because the menu image is applied in two places, but only one is being turned off on events. The menu image is applied to “.nav” and to “.nav .home a:hover” but the “.nav” class background is not adjusted. So it continues to show through the :hover state due to the transparent nature of the PNG.

    Is there a solution for this? Help is very much appreciated.

  77. I just want to one pixel image repeat.however,the result is’t enough to answer my question,all about css sprites links,I need you help? thank you!

  78. Hy there.

    I tooked the time to read all the comments and saw that nobody asked about my problem.

    I managed to addapt the menu to my website and get rid of absolute position. But i’m having trouble, since my menu is generated, every li has multiple clases. How can i make the js code to work ..is there a posibility to ajust in such a matter that will get only the first class of the li?

    Thank you for your help.

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