Steve Taylor photo

jQuery hover drop-down menus and the setTimeout issue

You know those days when you kind of know there’s plugins and whatnot aplenty to do something? But you’re feeling stubborn, and you’d rather re-invent the wheel? Today has been like that.

It’s not just being stubborn, though. Sometimes you want to re-invent the wheel so you know how to make better wheels yourself. In coding, you have to strike a balance between getting stuff done quickly and efficiently by relying on what’s already been done, and actually progressing your own skills.

Anyway, today I solved something that had been bugging me for a while: creating slick drop-down menus. I know CSS-based drop-downs, and they’re a good thing. But having got into jQuery recently, I wanted to get them right with scripting.

The main trick I’d missed was how to make sure that if the cursor passes quickly over a menu item, the “show menu” bit doesn’t get triggered—preventing shoddy, jittery menus popping up all over as you reach for the page scroller or something.

Well, after a few hours’ wrangling, here’s the code:

jQuery( document ).ready( function($) {
	var navTimers = [];
	$( "#nav-main ul.nav > li" ).hover(
		function () {
			var id = jQuery.data( this );
			var $this = $( this );
			navTimers[id] = setTimeout( function() {
				$this.children( 'ul' ).fadeIn( 300 );
				navTimers[id] = "";
			}, 300 );
		},
		function () {
			var id = jQuery.data( this );
			if ( navTimers[id] != "" ) {
				clearTimeout( navTimers[id] );
			} else {
				$( this ).children( "ul" ).fadeOut( 200 );
			}
		}
	);
});

A few words of explanation:

The navTimers array is crucial. The key to all this is storing a separate setTimeout() timer reference for each menu element that the cursor goes over.

OK, so #nav-main ul.nav > li selects the immediate children of my main nav menu list, and then I set up the hover functions.

The first line in each hover function uses the jQuery data() function, which returns a unique ID for the current element (just an integer, which seems to be related to the order of the elements). We use that as our key for the navTimers array.

In the “over” function, we create a reference to the jQuery object for the current element, so we can pass it through to the function inside setTimeout(). Then when initialize the setTimeout() itself, the function inside it doing two things:

  1. Triggers the jQuery fadeIn() effect.
  2. Clears its own reference in navTimers. (Actually I’m not sure if this is cleared automatically after the timer expires—I did this just to be safe.)

That 300 at the end of the setTimeout() is the number of milliseconds it’ll wait before triggering the fadeIn(). The 300 inside the fadeIn() is the number of milliseconds jQuery will take to do the fade.

The “out” function checks if there’s a timer set for the current element. If there is, it just clears it. This is the eventuality where the cursor has gone over and then out of the element’s space within our set limit (300 ms). If there isn’t a timer, i.e. the timer’s expired and the drop-down’s been shown, then it hides the drop-down.

Whew! Do let me know if you spot any issues, or ways of doing things better.

Oh, if you’re here looking for a ready-made drop-down menu solution, including markup and CSS… apologies, but this post is to help out other who are being stubborn and a re-inventing the wheel ;-). But really, if you can’t find a good plugin (I’m working within the WordPress world here by the way), the markup and CSS are the easy bits. Hopefully the above helps out with the tricky JavaScript.

UPDATE 29/7/11: It seems that as of jQuery 1.4, jQuery.data( elem ) no longer returns a unique ID for the element. I’ve not worked with the above code for a while, but I imagine it’s broken for 1.4. The new way that the data method works is to store key / value data associated with an element. The above code might be rewritten for 1.4 to store that timer objects in each element using this method. Please contact me if you come up with a new version!

9 comments

  1. roger avatar roger

    Hello,

    I tried your code for my css menu but it didn’t seem to work. I’m a jQuery noob. Do I have to customize the code anywhere for it to work with my menu?

  2. Steve Taylor avatar Steve Taylor

    Hi Roger, #nav-main ul.nav > li is the main bit you might need to customize, to match the list items in your nav menu. If this doesn’t work, maybe link to your example?

  3. roger avatar roger

    Hi Steve,

    I’d figured that that would probably be the area for customization but not sure how. I don’t have a link to my site as it is an internal one in our office but I got this from here:
    http://www.cssmenus.co.uk/dropline.html

    Except for the submenu descriptions I haven’t changed anything from the code.

  4. Steve Taylor avatar Steve Taylor

    That link you posted seems to be for a complete drop-downs system – which I don’t know anything about. The above code is really for people who are coding their own menus from scratch and need a bit of a nudge with the jQuery… I’m not sure how my code might interact with the Dropline system (evidently not very well!).

  5. roger avatar roger

    The id for my menu is ‘dropline’, I’ll try and substitue #nav-main with that and see if it works. If not I’ll try a few other alternatives and I’ll post the solution (if I figure one out, that is). Thanks,
    roger

  6. George avatar George

    in jQuery 1.4 there is delay option: delay(500) which will give you similar results

  7. Steve Taylor avatar Steve Taylor

    Yes, I saw this – great news! Although, I wonder if the main problem addressed above remains. It’s not the lack of a delay function that’s the real issue, it’s the fact that you need to also cancel the function that’s waiting to trigger if the mouse leaves the item before it’s been triggered. I’ve not looked into the jQuery 1.4 docs, though, maybe it handles this too.

  8. Leo avatar Leo

    You’re absolutely right about the delay function Steve – It just delays a function. If you hover out before the delay is over it will just continue regardless and you end up with a menu being shown without hovering over it.

    The problem I have with your method is the jquery.data() function doesn’t seem to be returned an integer, rather an object (on jquery 1.4.1).

  9. lucky_girl avatar lucky_girl

    Great post. You seem to have a good understanding that how to create a professional drop down menu. When I entering your blog, I felt this. Come on and keep writing your blog will be more attractive. To Your Success!

Comments are closed.