Skip to main content
Topic: Menus menyooz munyews - them things with all teh linkys in 'em. (Read 17775 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Menus menyooz munyews - them things with all teh linkys in 'em.

K, been thinking about bloody menus.

Generally, I quite like hover drops. They're a handy way of keeping things organised, as long as you don't go bonkers with them.

(IMO "bonkers" starts at three levels. Anything over that is getting silly, and you'd be better off rethinking how your navigation is arranged)

Problem with hover drops is that the code require to run them, if you want them to be accessible to a wide range of users, is not really all that simple. Raw CSS2 hover drops are not that good. They're far too twitchy for some people, and they have an annoyng tendency to flash at you when you're just on the way to somewhere else.

These problems can be largely mitigated with cunning use of CSS3, but the problem with that is that browsers we'll have to support for a while (namely IE8 and IE9) don't have any support for the required transitions.

Ok, so you can add javascript. We went through this already with the hoverIntent/Superfish combination. It works, and it works rather well, unless you happen to have a touchscreen of any sort. Hover drops of any kind pretty much suck on touchscreens. You can make them work, but it's no fun for the user. Touchscreens are becoming more and more important, and a default menu system that doesn't accommodate them that well is making less and less sense.

This has got me thinking that cleanest all-round solution is to go with click drops. They do require js, but they don't need tricks like hoverIntent or CSS3. The CSS and js required to run them is minimal. They are fully keyboard accessible if done sensibly.

In practice, using them is just as fast as using a hover drop. There's an extra click involved, but it doesn't really waste any noticeable amount of time. There are no problems with them opening or closing when you don't want them to, and no need for workarounds to try and avoid this. They can be made to work very well on touchscreen, as well as desktop/mouse.

Problem: use with javscript disabled.

Solution: use noscript tags to call one very small stylesheet, that reverts the menus to pure css2 hover drops, with extra keyboard fun provided by visuals for focused links in the drops. This can be done very simply, and should cover all bases.
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #1

In my understanding, folks were happy with this hover-thingie.

Any way we can implement an alternative solution only for browsers who require an alternative solution?
The best moment for testing your PR is right after you merge it. Can't miss with that one.

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #2

Can, but that's more stuffing around than a one size fits all solution.
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #3

K, I'll give you an example. Assuming the jQuery library is already called, this is the extra javascript required to run the current hover drops.

Code: [Select]
(function($) {
$.fn.hoverIntent = function(f,g) {
// default configuration options
var cfg = {
sensitivity: 8,
interval: 50,
timeout: 1
};
// override configuration options with user supplied object
cfg = $.extend(cfg, g ? { over: f, out: g } : f );

// instantiate variables
// cX, cY = current X and Y position of mouse, updated by mousemove event
// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
var cX, cY, pX, pY;

// A private function for getting mouse position
var track = function(ev) {
cX = ev.pageX;
cY = ev.pageY;
};

// A private function for comparing current and previous mouse position
var compare = function(ev,ob) {
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
// compare mouse positions to see if they've crossed the threshold
if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
$(ob).unbind("mousemove",track);
// set hoverIntent state to true (so mouseOut can be called)
ob.hoverIntent_s = 1;
return cfg.over.apply(ob,[ev]);
} else {
// set previous coordinates for next time
pX = cX; pY = cY;
// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
}
};

// A private function for delaying the mouseOut function
var delay = function(ev,ob) {
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
ob.hoverIntent_s = 0;
return cfg.out.apply(ob,[ev]);
};

// A private function for handling mouse 'hovering'
var handleHover = function(e) {

// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
var p = (e.type == "mouseenter" ? e.fromElement : e.toElement) || e.relatedTarget;
while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
if ( p == this ) { return false; }

// copy objects to be passed into t (required for event object to be passed in IE)
var ev = jQuery.extend({},e);
var ob = this;

// cancel hoverIntent timer if it exists
if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }

// if e.type == "mouseenter"
if (e.type == "mouseenter") {
// set "previous" X and Y position based on initial entry point
pX = ev.pageX; pY = ev.pageY;
// update "current" X and Y position based on mousemove
$(ob).bind("mousemove",track);
// start polling interval (self-calling timeout) to compare mouse coordinates over time
if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

// else e.type == "mouseleave"
} else {
// unbind expensive mousemove event
$(ob).unbind("mousemove",track);
// if hoverIntent state is true, then call the mouseOut function after the specified delay
if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
}
};

// bind the function to the two event listeners
return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover);
};
})(jQuery);

;(function($){
$.fn.superfish = function(op){

var sf = $.fn.superfish,
c = sf.c,
over = function(){
var $$ = $(this), menu = getMenu($$);
clearTimeout(menu.sfTimer);
$$.showSuperfishUl().siblings().hideSuperfishUl();
},
out = function(){
var $$ = $(this), menu = getMenu($$), o = sf.op;
clearTimeout(menu.sfTimer);
menu.sfTimer=setTimeout(function(){
o.retainPath=($.inArray($$[0],o.$path)>-1);
$$.hideSuperfishUl();
if (o.$path.length && $$.parents(['li.',o.hoverClass].join('')).length<1){over.call(o.$path);}
},o.delay);
},
getMenu = function($menu){
var menu = $menu.parents(['ul.',c.menuClass,':first'].join(''))[0];
sf.op = sf.o[menu.serial];
return menu;
},
// This next line is essential, despite the other code for arrows being removed.
// Changing the next line WILL break hoverIntent functionality. Very bad.
addArrow = function($a){$a.addClass(c.anchorClass)};

return this.each(function() {
var s = this.serial = sf.o.length;
var o = $.extend({},sf.defaults,op);
var h = $.extend({},sf.hoverdefaults,{over: over, out: out},op);

o.$path = $('li.'+o.pathClass,this).slice(0,o.pathLevels).each(function(){
$(this).addClass([o.hoverClass,c.bcClass].join(' '))
.filter('li:has(ul)').removeClass(o.pathClass);
});
sf.o[s] = sf.op = o;
$('li:has(ul)',this)[($.fn.hoverIntent && !o.disableHI) ? 'hoverIntent' : 'hover'](($.fn.hoverIntent && !o.disableHI) ? (h) : (over,out)).each(function() {})
.not('.'+c.bcClass)
.hideSuperfishUl();

var $a = $('a',this);
$a.each(function(i){
var $li = $a.eq(i).parents('li');
$a.eq(i).focus(function(){over.call($li);}).blur(function(){out.call($li);});
});
o.onInit.call(this);

}).each(function() {
var menuClasses = [c.menuClass];
$(this).addClass(menuClasses.join(' '));
});
};

var sf = $.fn.superfish;
sf.o = [];
sf.op = {};
sf.c = {
bcClass     : 'sf-breadcrumb',
menuClass   : 'sf-js-enabled',
anchorClass : 'sf-with-ul',
};
sf.defaults = {
hoverClass : 'sfhover',
pathClass : 'current',
pathLevels : 1,
delay : 600,
animation : {opacity:'show', height: 'show', width: 'show'},
speed : 200,
disableHI : false, // Leave as false. True disables hoverIntent detection (not good).
onInit : function(){}, // callback functions
onBeforeShow: function(){},
onShow : function(){},
onHide : function(){}
};
sf.hoverdefaults = {
sensitivity : 8,
interval    : 50,
timeout     : 1
};
$.fn.extend({
hideSuperfishUl : function(){
var o = sf.op,
not = (o.retainPath===true) ? o.$path : '';
o.retainPath = false;
var $ul = $(['li.',o.hoverClass].join(''),this).add(this).not(not).removeClass(o.hoverClass)
.find('>ul').hide().css('opacity','0');
o.onHide.call($ul);
return this;
},
showSuperfishUl : function(){
var o = sf.op,
sh = sf.c,
$ul = this.addClass(o.hoverClass)
.find('>ul:hidden').css('opacity','1');
o.onBeforeShow.call($ul);
$ul.animate(o.animation,o.speed,function(){o.onShow.call($ul);});
return this;
}
});

})(jQuery);

Plus this in the actual template:
Code: [Select]
				$(document).ready(function() {
// Menu drop downs.
$(".tabs ul").superfish();
});


Again, assuming jQuery is already present, this is the javascript required to run a really slick three level click drop.
Code: [Select]
				$(document).ready(function() {
// Click drop downs for touchscreen.
$(".touchscreen_drop>a").click(function(e) {
e.preventDefault();
$(this).closest(".touchscreen_drop").find(">ul").toggle("fast");
});
// Click anywhere else to close.
$(document).bind("click", function(e) {
var $clicked = $(e.target);
if (! $clicked.parents().hasClass("tabs_list"))
$(".touchscreen_drop>ul").hide("fast");
});
});


That's it. Nothing else. Will look good in any browser, and will work on touchscreen.

ETA: Oh and it also simplifies the markup and css. :)
Last Edit: December 25, 2012, 04:59:23 pm by Antechinus
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #4

Just as a FYI, I've changed this code a bit. That involved much swearing and much searching in dark and dismal corners of teh interwebz. 

Problem with the old code was that it wouldn't close a drop menu when you clicked on another tab. It'd close the droppy if you clicked the same top level tab again, or if you clicked anywhere outside the menu area, but not if you clicked on one of the other tabs. This was ok, but not really up to scratch.

So, after looking through umpteen topics on Stack Overflow and elsewhere, I've sorta learned enough about jQuery to get that sorted without losing any of the other niftiness, and without bloating the code to daft levels. 

New code follows. Works perfectly everywhere. The fade effects are just to stop the transitions being less jaggy on the eyes. I found that when opening another droppy and hiding the old one, having a straight hide, or a fast toggle, was disconcerting. The slower fade feels good. The old droppy sorta just disappears without smacking you in the face about it. The new one comes in fast enough to not be annoying.

Code: [Select]
			$(document).ready(function() {

//Remove noscript css file when js is enabled (file reverts click drops to css2 hover drops).
$("#styleNoscript").attr("disabled", "disabled");
$("#styleNoscript").remove();

// Click drop downs.
$(".touchscreen_drop>a").click(function(e) {
e.preventDefault();
$(this).closest(".touchscreen_drop").siblings(".touchscreen_drop").find("ul").fadeOut(450);
$(this).closest(".touchscreen_drop").find(">ul>li>ul").fadeOut(250);
$(this).closest(".touchscreen_drop").find(">ul").toggle(250);
});

// Click anywhere else to close.
$(document).bind("click", function(e) {
var $clicked = $(e.target);
if (! $clicked.parent().hasClass("touchscreen_drop"))
$(".touchscreen_drop>ul").hide();
});
});

The #styleNoscript bit at the top of that block is just to remove a small noscript.css file (600 bytes worth) that reverts the click drops to straight css2 hover drops when js is disabled. This seemed like the best fallback.

PS: I'm quite chuffed about this code. From what I've seen from looking in a lot of places, it's probably one of the least verbose click drop codes, with some of the best functionality around. Can probably be made a bit leaner too.
Last Edit: December 28, 2012, 01:17:34 am by Antechinus
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #5

Got it leaner. This is about as good as it gets.

Code: [Select]
			$(document).ready(function() {

//Remove noscript css when js is enabled (css reverts click drops to hover drops).
$("#styleNoscript").attr("disabled", "disabled").end().remove();

// Click drop downs.
$("a.dropmenu_trigger").click(function(e) {
e.preventDefault();
var parent_li = $(this).closest("li.dropmenu")
parent_li.find(">ul").toggle(250).end().find(">ul>li>ul").hide().end().siblings().find(">ul").fadeOut(450);
});

// Click anywhere else to close.
$(document).bind("click", function(e) {
var $clicked = $(e.target);
if (! $clicked.parents().is("li.dropmenu"))
$("li.dropmenu>ul").hide();
});

});

ETA: Funny thing about this code. Turns out it's extensible to an infinite number of levels, without needing any extra css or js. Bloody thing just keeps working. I didn't expect that. :)
Last Edit: December 29, 2012, 02:27:00 am by Antechinus
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #6

Methinks I'm getting carried away with this, but it's a great way to learn jQuery basics (considering I knew absolutely nothing about jQ a few days ago).

Anyway, turns out the old style of using click is slow compared to newer options. This no good:
Code: [Select]
$("a.dropmenu_trigger").click(function(e)

This much better:
Code: [Select]
$("a.dropmenu_trigger").live("click", function(e)

For some bizarre jQuery reason me no understand, using .live(stuff goes here) is much faster than using plain old click. Drawback is it wont allow multiple events to be chained, unlike bind(), but I didn't need that anyway.

I also realised (Great Moments in D'oh) that I didn't need class names in some places, because the nearest <li> would always have the same class name anyway, so I might as well just get by tag instead. That's what it looks for first anyway so (in this particular example) there was no point carrying the overhead of making it look for the class as well.

Combine that with what I've found out about the document ready shiz and the new code now looks like this:

Code: [Select]
			$.fn.ready(function() {

//Remove noscript css when js is enabled (css reverts click drops to hover drops).
$("#styleNoscript").attr("disabled", "disabled").end().remove();

// Click drop downs.
$("a.dropmenu_trigger").live("click", function(e) {
e.preventDefault();
var $parent_li = $(this).closest("li")
$parent_li.find(">ul").toggle(200).end().find(">ul>li>ul").css("display","none").end().siblings().find(">ul").fadeOut(400);
});

// Click anywhere else to close.
$(document).live("click", function(e) {
var $clicked = $(e.target);
if (! $clicked.parents().is("li.dropmenu"))
$("li.dropmenu>ul").css("display","none");
});

});

It seems faster and smoother, but I may be tripping. I should benchmark it some time and see what happens.

ETA: Oh and one odd thing was that I had to use styleNoscript for the id of the sheet. If I used the same format as I was using for all the sheet names (style_noscript.css turned into id="style_noscript") it made Firefox throw a javascript error. Namely this sucker:

Code: [Select]
0x80004005 (NS_ERROR_FAILURE) [nsIDOMWindow.content]

Dis javascript is weird. :P
Last Edit: December 29, 2012, 05:51:04 am by Antechinus
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #7

Oh currently I'm thinking this article makes a lot of sense, although to be fair some of the complaints he mentions are just due to bad coding of hover drops (of which there is a lot around), and can be avoided if the coder gives a rat's and/or knows better (a lot of them apparently don't).

http://uxmovement.com/navigation/why-hover-menus-do-users-more-harm-than-good/
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #8

There are more sense In the comments than in his article lol. :) You can't expect people to think clicking 2-3 times is faster than just hovering...

I think a nice combo of buttons + hidden/upshrinked sections is the way to go, even for navigation. in win8 there are no hover menus at all, and that will be the common trend I think, since it will work on both touch/non-touch devices and avoid space/intent/touch problems.


Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #9

Umm, "nice combo of buttons + hidden/upshrinked sections" is exactly what a click-through menu is. ;) Also, a lot of the comments were agreeing with the article.  ;D 

It's not necessarily faster, but it's more predictable. That can sometimes mean is is faster to get the desired result, because you're not dealing with getting rid of an undesired result (meaning hover drop opening when you don't want it to).

Anyway I agree that hover drops are probably on the way out, for the reasons you mention.

ETA: Then again, if using hoverIntent to make multiple hover drops behave (and they're pretty crappy without it) then click menus are easily as fast. I've been using them intensively for about a week now and am loving the buggers.
Last Edit: December 30, 2012, 07:03:36 pm by Antechinus
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #10

Yeah, I realized that after I wrote it haha. :) Anyways, not sure how i will go with menus..but its in that direction most likely. Already I am trying out a menu that slide down a horisontal submenu. Horisontal because then it won't hog so much vertical space, and slide to ease into it instead of directly in.

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #11

Yes I've thought of droplines before (who hasn't?) but I find them rather awkward. The sharp dogleg turns between layers bug me a bit. They don't feel as natural to use, even though they take less vertical space. If using a click through system, I'm not sure the vertical space is a big issue. You can mouse off and scroll where you like.

I've animated mine slightly. 200ms toggle on/off. Quick, but doesn't whack you in the face.
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #12

Back from experimenting on this - I checked out your site, the menus feel rather good..but I hesitate when hovering the menu, like I expect it to show lol. That is something to consider, maybe you should not even trigger hover on top items, but instead be real buttons you need to click.

Anyways, have been toying with droplines..erm, not the best to manoeuvre. I then tried a new approach without hover/drops at all, and while it kinda works, it feel a little off. Attached screens of it. The colors don't work that well, although i like the button feel there. Will have to think on a bit.

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #13

Quote from: Bloc – Back from experimenting on this - I checked out your site, the menus feel rather good..but I hesitate when hovering the menu, like I expect it to show lol. That is something to consider, maybe you should not even trigger hover on top items, but instead be real buttons you need to click.

I don't understand what you mean by this. I'm not triggering hover on the top level. They are real buttons you need to click.

The only reason you find it odd is because you're used to hover menus there. If you were used to using vB heavily (just as an example) you'd find hover menus odd. If you used my menus for a few days as part of just being involved in a site, I think you'd get used to them quickly. I found them a little odd at first, but they're fine now.
Master of Expletives: Now with improved family f@&king friendliness! :D

Sources code: making easy front end changes difficult since 1873. :P

Re: Menus menyooz munyews - them things with all teh linkys in 'em.

Reply #14

Yes, you are. :) When I hover the buttons get a bevelled appearance..and I then expect them to show something underneath, which doesn't happen. It prob. would be better if that hover didn't occur, so you press it instead and THEN get the submenu.

We are talking same theme here? the "test theme"?