Suckerfish menus with hide delay

You’re using Suckerfish Dropdown menus but you would like to prevent the dropdown menus from disappearing the moment the users mouse steps 1 pixel outside the dropdown?

This question is especially relevant with multi-level dropdown menus; multi-level menus and laptop track-pads are something I’ll never get used to!

The code presented below is designed to be a drop-in replacement for the standard suckerfish Javascript. The original code was only required to make MSIE work correctly, the replacement code targets all browsers. If Javascript has been disabled by the user, then the CSS will function as before.

sfHover = function() {
	var timeout = 600;
	var cssClass = "sfhover";

	var queue = [];
	var reCSS = new RegExp("\\b" + cssClass + "\\b");
	var sfEls = document.getElementById("nav").getElementsByTagName("li");
	for (var i=0; i<sfEls.length; i++) {

		// mouseover and mouseout handlers for regular mouse based interface.
		sfEls[i].onmouseover = function() {
			queueFlush();
			this.className += " " + cssClass;
		}
		sfEls[i].onmouseout = function() {
			queue.push([setTimeout(queueTimeout, timeout), this]);
		}

		// focus and blur handlers for keyboard based navigation.
		sfEls[i].onfocus = function() {
			queueFlush();
			this.className += " " + cssClass;
		}
		sfEls[i].onblur = function() {
			queue.push([setTimeout(queueTimeout, timeout), this]);
		}

		// click event handler needed for tablet type interfaces (e.g. Apple iPhone).
		sfEls[i].onclick = function(e) {
			if (this.className.search(reCSS) == -1) {
				// CSS not set, so clear all sibling (and decendants) menus, and then set CSS on this menu...
				var elems = this.parentNode.getElementsByTagName("li");
				for (var i=0; i<elems.length; i++) {
					elems[i].className = elems[i].className.replace(reCSS, "");
				}
				this.className += " " + cssClass;
			} else {
				// CSS already set, so clear all decendant menus and then this menu...
				var elems = this.getElementsByTagName("li");
				for (var i=0; i<elems.length; i++) {
					elems[i].className = elems[i].className.replace(reCSS, "");
				}
				this.className = this.className.replace(reCSS, "");
			}
			if (e && e.stopPropagation)
				e.stopPropagation();
			else
				window.event.cancelBubble = true;
		}
	}

	queueFlush = function () {
		while (queue.length) {
			clearTimeout(queue[0][0]);
			queueTimeout();
		}
	}

	queueTimeout = function() {
		if (queue.length) {
			var el = queue.shift()[1];
			el.className = el.className.replace(reCSS, "");
		}
	}
}
addLoadEvent(sfHover);

The code also includes handlers for focus and blur events, to allow site visitors to tab through the menus without using a mouse.

And finally, a click event handler is added to parent menus so that they can be opened on devices that don’t support hover events such as the iPhone/iPod Touch. This might cause a problem if your parent menu item is also a link however!

Oh… and finally finally, the above code calls the an addLoadEvent() function to force sfHover to run on page load. If you don’t already have a similar function, here’s one you can use:

function addLoadEvent(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			oldonload();
			func();
		}
	}
}

UPDATE 2008-05-19: I have uploaded a very basic example page here, and the JavaScript is available for download here.

Be Sociable, Share!

14 Responses to “Suckerfish menus with hide delay”

  1. Solar G Says:

    Many thanks for publishing this! The SF menus are fairly unusable without a delay and I was struggling to concoct my own JS to solve it. Shame it’s the last on Google when searching for “suckerfish mouseout delay”. Anyway, much obliged to you.

  2. Carrie Phillips Says:

    I implemented your menu on a site and it was exactly what I was looking for from a delay java script. However, I did run into one problem when using Firefox. If you navigate to one of the sub menu pages and then click the back button to return to the previous page, the sub menu is still open and will remain open. I would guess this is a result of the onload not being triggered by the back button.

    Have you run into this problem before? I’d rather not have to force the page to refresh or reload if possible when a user hits the back button. It works fine in IE, so I’m not sure why Firefox handles it oddly.

    Please, email me if you have a solution.

    Thanks,
    Carrie

  3. admin Says:

    Hi Carrie,

    There’s quite a few things I want to re-work on this code at some point, including fixing this issue. As a work-around, if you add this line:

    window.onunload=function(){queueFlush()}

    near the top of the sfHover function, it should fix the problem for you. Hope that helps!

    Peter.

  4. Carrie Phillips Says:

    Thanks for the quick response. Quick fix worked perfectly. I’m looking forward to your revamped version of this script, but even as is it’s the best script I’ve come across for delay drop downs.

    Thanks for everything,
    Carrie

  5. Ryan Says:

    Thanks for the script. I haven’t tried it out yet, but if it works as well as you describe, then I may integrate it (with credit back to you included) into my ‘Multi-level Navigation Plugin’ for WordPress … http://pixopoint.com/multi-level-navigation/

    I currently use the Superfish jQuery plugin to implement the delay effect, but your script would be a nice addition for those users who don’t want to use jQuery.

    A new version is due out some time before the end of the year so I’ll hopefully get it running with your script by then.

  6. Bill Says:

    Thanks very much for this. Newb question: Is it possible to drop this into the suckerfish solution I am using in Joomla?

  7. Peter Ryan Says:

    Hi Bill,

    It should do… but I’m not familiar with Joomla myself or it’s suckerfish implementation. You might need to change the cssClass from “sfhover” to something else, and you’ll need to remove the existing suckerfish JavaScript, but otherwise, this should be a drop in replacement.

    You might want to take account of the comments above to fix a problem with menus remaining open when the user presses the back-button on their browser…. at some point, when I get some free time, I *will* re-work this code!!

    Peter.

  8. max mcdowell Says:

    Thank you Peter,

    It works perfectly, including for-firefox modification, even though I know no javascript and can only cut and paste it.
    My three level menu is now calm and usable.

    Max

  9. Ryan Says:

    Thanks for the great script.

    Do you have any idea how to get it working with multiple menus with different ID’s on the same page?

  10. Peter Ryan Says:

    Hi Ryan,

    If you find the line that sets the sfEls line and comment it out, like so:

    // var sfEls = document.getElementById("nav").getElementsByTagName("li");

    and then add these lines in below it:

    var sfEls = [], elems, i;

    elems = document.getElementById("nav").getElementsByTagName("li");
    for (i=0; i<elems.length; i++) sfEls = sfEls.concat(elems[i]);

    elems = document.getElementById("nav2").getElementsByTagName("li");
    for (i=0; i<elems.length; i++) sfEls = sfEls.concat(elems[i]);

    then that should work! This of course assumes your IDs are “nav” and “nav2″, but those are easily changed. If you have more menus then just copy and past those two lines and change the ID as appropriate.

    I’ve tested this only in Firefox…. so you might want to check it on other browsers before rolling it out! But it should work fine. ;)

    Hope that helps.

  11. Tim Says:

    How’s it going. I’m using a slightly different superfish script. I am having the same problem as “carry” in a previous post where everytime I hit the back button the menu along with the sub is present. I don’t have an actual sfHover function within my code. Any insight?

    Thanks.

  12. Peter Ryan Says:

    Hi Tim,

    Are you saying you’re using a different script from the one here? And you want support? For a script I can’t see or know the name of? ;)

    Okay… the issue above was because the sf-delay script adds CSS class names to the DOM on all browsers, whereas the original sfhover function only did anything when the browser was IE. The problem is, Firefox (and possibly other browsers) as opposed to IE, will return to the same DOM when you press the back button… so the CSS class names show made the menu visible are still in place. But the JavaScript events that should’ve removed those CSS class names after the delay, have been lost.

    So the fix was to force the function that removed the CSS class names to execute when the page unloads. So when the user selects a menu item, the DOM is returned to its original state before the new page is loaded.

    Hope that helps some! :D

    Peter.

  13. Tim Says:

    Hahaha. Very good point. I guess I should feel lucky you even reponded, but it’s much appreciated.

    Thanks Peter!

  14. jonny Says:

    Anyone know how to make a 2 second delay before the first drop down appears on hover?