Large horizontal drop-down menus are quite popular these days, especially in e-commerce websites. However, they can cause ergonomic design troubles by launching on unintentional mouseovers. Which could be very irritating.
Fixing this problem by implementing a mouseover delay on the trigger (usually tabs of a menu) is therefore well-recommended.
For starters, using pure CSS2 solution with classic pseudo-class :hover is not in our situation a good solution, as you will have difficulties to put a delay on these events. Additionally, this pseudo-class isn’t supported on IE6. You would then have to find a hack in order to make :hover effective for IE6… yada yada.
CSS3 is coming out with a nice transition-delay property. I didn’t test it. Of course we all know that CSS3 is not by far yet the common standard. FYI, IE9 won’t be supported on Windows XP, and that’s not very helpful, to say the least.
You therefore have to use JavaScript to fix your little problem.
First, you can use jQuery, which comes with an excellent plugin, hoverIntent. Highly customizable, it’ll do everything you ask for.
If your website is powered by Prototype, you might not want to install jQuery only to fix this issue.
So is here below a bunch of code which, with a little customization depending of your HTML structure, can help.
Prototype 1.6.1 supports mouseenter/mouseleave events, and CSS nth-child pseudo-class. I’ll use them.
HTML layout example:
<ul id="menu">
<li><span>Tab 1</span>
<ul><li>Sub menu 1</li>
</ul>
</li>
<li><span>Tab 2</span>
<ul><li>Sub menu 2</li>
</ul>
</li>
<li><span>Tab 3</span>
<ul><li>Sub menu 3</li>
</ul>
</li>
<li><span>Tab 4</span>
<ul><li>Sub menu 4</li>
</ul>
</li>
</ul>
Now, JavaScript.
Set the lenght of the delay in the menutabdelay variable.
var menutabdelay = 500;
function visible(menutabnumber){
$$('ul#menu > li:nth-child('+menutabnumber+') > ul').each(function(element){
$(element).setStyle('visibility: visible;');
});
}
function showsubmenu(element, menutabnumber){
Event.observe(element, 'mouseenter', function(){
timedelay = setTimeout(function(){visible(menutabnumber)}, menutabdelay);
});
Event.observe(element, 'mouseleave', function(){
clearTimeout(timedelay);
$$('ul#menu > li > ul').each(function(element){
$(element).setStyle('visibility: hidden;');
});
});
}
function showsubmenutrigger() {
var tabs = $$('ul#menu > li');
for(numberoftabs=1; numberoftabs<=tabs.length; numberoftabs++){
$$('ul#menu > li:nth-child('+numberoftabs+')').each(function(element){
var menutabnumber = numberoftabs;
showsubmenu(element, menutabnumber);
});
}
}
Event.observe(window, 'load', showsubmenutrigger);
View demo
If you use a Prototype version prior to 1.6.1, these versions don’t support mouseenter/mouseleave events, so you have some fix to apply.
For information, mouseenter/mouseleave events are naturally supported by IE but not by other browsers.
You could ask, why don’t you use mouseover/mouseout events, supported by all browsers. My answer would be, it’s impossible because these events naturally bubble inside the element, and it’s big problem here because of the delay.
Long story short, see how to implement the fix below:
Element.addMethods({
onmouseenter: function(element,observer) {
element = $(element);
element.observe('mouseover',function(evt,currentTarget) {
var relatedTarget = $(evt.relatedTarget || evt.fromElement);
if( relatedTarget!=currentTarget && relatedTarget.childOf(currentTarget)==false ) {
observer();
}
}.bindAsEventListener({},element));
return element;
},
onmouseleave: function(element,observer) {
element = $(element);
element.observe('mouseout',function(evt,currentTarget) {
var relatedTarget = $(evt.relatedTarget || evt.toElement);
if( relatedTarget!=currentTarget && relatedTarget.childOf(currentTarget)==false ) {
observer();
}
}.bindAsEventListener({},element));
return element;
}
});
var menutabdelay = 500;
function visible(menutabnumber){
$$('ul#menu > li:nth-child('+menutabnumber+') > ul').each(function(element){
$(element).setStyle('visibility: visible;');
});
}
function showsubmenu(element, menutabnumber){
Event.observe(element, 'mouseenter', function(){
timedelayIE = setTimeout(function(){visible(menutabnumber)}, menutabdelay);
});
Element.onmouseenter(element,function(){ // No IE
timedelay = setTimeout(function(){visible(menutabnumber)}, menutabdelay);
});
Event.observe(element, 'mouseleave', function(){
clearTimeout(timedelayIE);
$$('ul#menu > li > ul').each(function(element){
$(element).setStyle('visibility: hidden;');
});
});
Element.onmouseleave(element,function(){ // No IE
clearTimeout(timedelay);
$$('ul#menu > li > ul').each(function(element){
$(element).setStyle('visibility: hidden;');
});
});
}
function showsubmenutrigger() {
var tabs = $$('ul#menu > li');
for(numberoftabs=1; numberoftabs<=tabs.length; numberoftabs++){
$$('ul#menu > li:nth-child('+numberoftabs+')').each(function(element){
var menutabnumber = numberoftabs;
showsubmenu(element, menutabnumber);
});
}
}
Event.observe(window, 'load', showsubmenutrigger);
View demo (Prototype 1.6.0.3)