News:

SMF 2.1.4 has been released! Take it for a spin! Read more.

Main Menu

Stuff for playing with (here be mutants)

Started by Antechinus, June 16, 2014, 10:21:53 PM

Previous topic - Next topic

Bloc

Thanks for the tip about max-height. :) Have implemented it and it works great! That means every upshrink I use can be a nice slide instead of the immediate jump.

Antechinus

Cool. Didn't know about it myself until you prompted me to look. Still learning new stuff, which is good. :)

I might play with it myself for some things. After looking around a bit, it seems doing animation effects with CSS3 is generally faster and lighter than doing them with jQuery or similar. So, even if using some javascript for touchscreen or a11y or whatever, having the eye candy side of it handled by CSS3 looks to be the best option.

Bloc

I am def. onboard with exchanging some of the js animation with CSS counterparts - adding plugins to JQuery/Mootools quickly adds up. Its easier to customize too, even remove alltogether.


Yeah, its working well indeed. If you need the modified code to exchange CSS classes instead of setting display: none/block, I added it below. You use the exact same setup as for smc_Toggle..but call urb_Toggle instead. The js code you put in theme.js, and the CSS to the stylesheet.

the call then looks like:

// Create the news fader toggle.
var smfNewsFadeToggle = new urb_Toggle({
bToggleEnabled: true,
bCurrentlyCollapsed: ', empty($options['collapse_news_fader']) ? 'false' : 'true', ',
aSwappableContainers: [
\'news_container\'
],
aExtraClass: [
\'forumwidth\'
],
aSwapImages: [
{
sId: \'newsupshrink\',
srcExpanded: smf_images_url + \'/collapse.gif\',
altExpanded: ', JavaScriptEscape($txt['upshrink_description']), ',
srcCollapsed: smf_images_url + \'/expand.gif\',
altCollapsed: ', JavaScriptEscape($txt['upshrink_description']), '
}
],
oThemeOptions: {
bUseThemeSettings: ', $context['user']['is_guest'] ? 'false' : 'true', ',
sOptionName: \'collapse_news_fader\',
sSessionVar: ', JavaScriptEscape($context['session_var']), ',
sSessionId: ', JavaScriptEscape($context['session_id']), '
},
oCookieOptions: {
bUseCookie: ', $context['user']['is_guest'] ? 'true' : 'false', ',
sCookieName: \'newsupshrink\'
}
});

Notice there is a extra parameter called ExtraClass, thats where you out the extra CSS classes you want on the container, since the routine effectively just replace the class with urb_xxx, everything else there is eradicated. I could make the JS check and tack it on..but found this approach more safe.

The actual JS code:

// *** urb_Toggle class.
function urb_Toggle(oOptions)
{
this.opt = oOptions;
this.bCollapsed = false;
this.oCookie = null;
this.init();
}

urb_Toggle.prototype.init = function ()
{
// The master switch can disable this toggle fully.
if ('bToggleEnabled' in this.opt && !this.opt.bToggleEnabled)
return;

// If cookies are enabled and they were set, override the initial state.
if ('oCookieOptions' in this.opt && this.opt.oCookieOptions.bUseCookie)
{
// Initialize the cookie handler.
this.oCookie = new smc_Cookie({});

// Check if the cookie is set.
var cookieValue = this.oCookie.get(this.opt.oCookieOptions.sCookieName)
if (cookieValue != null)
this.opt.bCurrentlyCollapsed = cookieValue == '1';
}

// If the init state is set to be collapsed, collapse it.
if (this.opt.bCurrentlyCollapsed)
this.changeState(true, true);

// Initialize the images to be clickable.
if ('aSwapImages' in this.opt)
{
for (var i = 0, n = this.opt.aSwapImages.length; i < n; i++)
{
var oImage = document.getElementById(this.opt.aSwapImages[i].sId);
if (typeof(oImage) == 'object' && oImage != null)
{
// Display the image in case it was hidden.
if (oImage.style.display == 'none')
oImage.style.display = '';

oImage.instanceRef = this;
oImage.onclick = function () {
this.instanceRef.toggle();
this.blur();
}
oImage.style.cursor = 'pointer';

// Preload the collapsed image.
smc_preCacheImage(this.opt.aSwapImages[i].srcCollapsed);
}
}
}

}

// Collapse or expand the section.
urb_Toggle.prototype.changeState = function(bCollapse, bInit)
{
// Default bInit to false.
bInit = typeof(bInit) == 'undefined' ? false : true;

// Handle custom function hook before collapse.
if (!bInit && bCollapse && 'funcOnBeforeCollapse' in this.opt)
{
this.tmpMethod = this.opt.funcOnBeforeCollapse;
this.tmpMethod();
delete this.tmpMethod;
}

// Handle custom function hook before expand.
else if (!bInit && !bCollapse && 'funcOnBeforeExpand' in this.opt)
{
this.tmpMethod = this.opt.funcOnBeforeExpand;
this.tmpMethod();
delete this.tmpMethod;
}

// Loop through all the images that need to be toggled.
if ('aSwapImages' in this.opt)
{
for (var i = 0, n = this.opt.aSwapImages.length; i < n; i++)
{
var oImage = document.getElementById(this.opt.aSwapImages[i].sId);
if (typeof(oImage) == 'object' && oImage != null)
{
// Only (re)load the image if it's changed.
var sTargetSource = bCollapse ? this.opt.aSwapImages[i].srcCollapsed : this.opt.aSwapImages[i].srcExpanded;
if (oImage.src != sTargetSource)
oImage.src = sTargetSource;

oImage.alt = oImage.title = bCollapse ? this.opt.aSwapImages[i].altCollapsed : this.opt.aSwapImages[i].altExpanded;
}
}
}

if ('aSwappableContainers' in this.opt)
{
// Now go through all the sections to be collapsed.
for (var i = 0, n = this.opt.aSwappableContainers.length; i < n; i++)
{
if (this.opt.aSwappableContainers[i] == null)
continue;

var oContainer = document.getElementById(this.opt.aSwappableContainers[i]);
if (typeof(oContainer) == 'object' && oContainer != null)
oContainer.className = bCollapse ? 'urb_hide' : 'urb_show';

if (this.opt.aExtraClass != null)
oContainer.className +=  ' ' + this.opt.aExtraClass;
}
}

// Update the new state.
this.bCollapsed = bCollapse;

// Update the cookie, if desired.
if ('oCookieOptions' in this.opt && this.opt.oCookieOptions.bUseCookie)
this.oCookie.set(this.opt.oCookieOptions.sCookieName, this.bCollapsed ? '1' : '0');

if ('oThemeOptions' in this.opt && this.opt.oThemeOptions.bUseThemeSettings)
smf_setThemeOption(this.opt.oThemeOptions.sOptionName, this.bCollapsed ? '1' : '0', 'sThemeId' in this.opt.oThemeOptions ? this.opt.oThemeOptions.sThemeId : null, this.opt.oThemeOptions.sSessionId, this.opt.oThemeOptions.sSessionVar, 'sAdditionalVars' in this.opt.oThemeOptions ? this.opt.oThemeOptions.sAdditionalVars : null);
}

urb_Toggle.prototype.toggle = function()
{
// Change the state by reversing the current state.
this.changeState(!this.bCollapsed);
}


..and the CSS:

/* CSS collapsing  */
.urb_show, .urb_hide {
max-height: 200px;
-webkit-transition: all 400ms ease;
-moz-transition: all 400ms ease;
-ms-transition: all 400ms ease;
-o-transition: all 400ms ease;
transition: all 400ms ease;
overflow: hidden;
}
.urb_hide { max-height: 0px; }


Antechinus

Thanks for that.

Speaking of js and a11y (which I was :D) after playing around with the CSS I got a really good no-js fallback for keyboard/tab activation of the drop menus. Looks exactly like normal hover activation, except that the drop menu's ul wrapper extends off the left side of the screen. Link styling and positioning isn't affected, and it all works normally when js is switched back on, or when using hover activation of the drops. It's currently set up with Superfish but should be adaptable for any js menu.

The trick is to get the positioning right on focus, without borking things on hover. I tried just using a z-index shift to keep lateral positioning constant, but that introduced other problems and didn't work well. So, I had to stick with using left positioning to hide the ul off hover, and a left margin on the anchor when focused (with js off). That then needed an extra declaration to cancel the left margin on the anchor when hovering over the li and the anchor was focused (otherwise anchor disappears off the right side of the screen when you click the link). Also, you need to declare left positioning and margins in pixels, not em, otherwise it will play havoc with your positioning (and your brain) if you are using different font sizes in different menu levels.

You can do this without having the ul wrapper visible at all (forgotten how I did that before) but I found the disembodied links appearing out of nowhere looked more disconcerting than the ul extending off the left of the screen. I think the latter is a better solution for all round user-friendliness.

Anyway CSS looks like this. I've used basically the same classes as I used for Elk's menu system, since they are clearly descriptive for people unfamiliar with drop menu coding.

/* Coding for the drop menus (main, admin, etc) */
/* -------------------------------------------- */
.dropmenu {
margin: 0;
padding: 0 6px;
}
#adm_submenus {
padding: 6px;
}
.dropmenu:after {
display: block;
clear: both;
content: "";
}

/* List element <li> declarations */
.listlevel1, .listlevel2, .listlevel3 {
position: relative;
list-style: none;
}
.listlevel1 {
margin: 0 2px 0 0;
padding: 0 0 4px 0;
float: left;
}

/* Visible link declarations */
.linklevel1, .linklevel2, .linklevel3 {
display: block;
padding: 0 10px;
font-size: 0.857em;
line-height: 2em;
}
.linklevel1 {
border: 1px solid transparent;
}
.linklevel2, .linklevel3 {
width: 17em;
line-height: 2em;
}
.linklevel2.subsections:after {
position: absolute;
right: 5px;
content: ">"
}

/* The drop menu backgrounds */
.menulevel2, .menulevel3 {
z-index: 99;
position: absolute;
top: 100%;
left: -3002px;
padding: 6px 0;
background: #000 url(../images/theme/menu_gfx.png) 0 -220px repeat-x;
border: solid 1px #4F4131;
border-radius: 4px;
box-shadow: 2px 3px 5px 1px #000;
}
#admin_menu .menulevel3 {
margin: -2em 0 0 15em;
}

/* The active button */
.active {
font-weight: bold;
}
.linklevel1.active,
/* The hover effects */
.linklevel1:hover, .linklevel1:focus, .listlevel1:hover>.linklevel1 {
background: #222;
color: #cfc2a8;
text-decoration: none;
border: 1px solid #333;
border-radius: 4px;
}
.linklevel1.active:hover, .linklevel1.active:focus {
background: #333;
color: #ffefcf;
border: 1px solid #645441;
}

/* The hover effects on level 2 and 3 */
.linklevel2:hover, .listlevel2:hover>.linklevel2, .linklevel3:hover {
background: #222 url(../images/theme/main_block.png) no-repeat -20px -280px;
color: #cfc2a8;
text-decoration: none;
}
.listlevel1:hover .menulevel2, .listlevel1.sfhover .menulevel2,
.listlevel2:hover .menulevel3, .listlevel2.sfhover .menulevel3 {
left: -2px;
}

/* The focus effects on level 2 and 3 */
.linklevel2:focus, .linklevel3:focus {
margin: 0 0 0 3000px;
background: #222 url(../images/theme/main_block.png) no-repeat -20px -280px;
color: #cfc2a8;
text-decoration: none;
}
.linklevel3:focus {
margin: 0 0 0 6000px;
}
.listlevel2:hover>.linklevel2:focus, .listlevel2.sfhover>.linklevel2:focus,
.listlevel3:hover>.linklevel3:focus, .listlevel3.sfhover>.linklevel3:focus {
margin: 0;
}


The important bits for the no-js keyboard stuff are the last three declarations. Screenies below.

ETA: Oh and the sidebar menu flyouts use the same general positioning as the drop menus, so this also works with the "advanced sidebar", without requiring any extra declarations for that.

Antechinus

Ok, so got sick of js for menus, since I have decent keyboard a11y without it. Tried that animating max-height trick. There's a catch with it: since it relies on overflow: hidden; it borks three level menus (the third level gets clipped at the boundary of the second level). Transitions don't work on the overflow property either.

I tried out various daft ideas, but it looks like there's no way around it without borking the keyboard a11y. So, that effectively rules out eye candy animations for three level menus with pure CSS3. To get eye candy animations and good keyboard a11y, you'll still need javascript. Two level menus are no problem though.

Bloc

Yes, I realized this too, when working with it. I think the menus can be semi-animated though, by using transitions on the opacity combined with some slight side-positioning on 3-level menus. This is a CSS trick on dropdowns I've used before, after finding it on the net. :D :P

I like the approach of using :link and :target, in fact I already use that, I found a project for having a CSS driven lightbox. You know, getting full-size attachments shown instantly. I think it works great there, although the images have to be loaded on pageload - but hidden in CSS. Still, by the time someone press it, it should have downloaded so the experience is immediate anyway.

The flyout menu is great! I want to use that too, on touch-device-only, instead of trying to squeeze in all the items there. Simplification is the key to a good mobile experience IMHO.

Antechinus

No can do opacity trix when wanting good keyboard a11y. It gets fiendishly complicated. If you hide the ul background by using opacity, you then have no way of getting visibility back on the ul content, because you can't track back up the tree when you get focus on one of the anchors. No can see nuffin forever. :P

Ok, so say you get cunning and declare everything as RGBA. You can then ensure you have visible links on focus, by changing the alpha value on the anchor colour and background, but you also have to take care of hover, so that means more alpha transitions there too. Also, when someone is using keyboard access they want things instantly, not with whoopsy Venetian blinds, so any transition on focus will conflict with your hover transition. Also, what happens off hover or off focus? Linkys disappear again. Yay!

There is probably a workaround if you try hard enough, and if your CSS doesn't preclude it by nature (ie: no images) but it'll only be a straight fade in. Can't do roll-ups.

Bloc

Heh, after re-reading your post about z-index trouble I realized I had a simialr problem using dropdowns on topics - when hovering over the avatar(really a menu item a bit below though) the menu actually would overlap the next post - if it was short enough. Of course i could make the dropdown have its item horizontal, but wanted that vertical look. 

I had to do some PHP things there - by adding inline z-index to each menu, and that each had exactly 1 more than the former... :D I know, its hack'ish as hell lol, but it works as long as no-one use 70-80++ posts per topic page..and who does that? ::) 

Quote from: Antechinus on June 19, 2014, 04:18:50 AM
No can do opacity trix when wanting good keyboard a11y. It gets fiendishly complicated. If you hide the ul background by using opacity, you then have no way of getting visibility back on the ul content, because you can't track back up the tree when you get focus on one of the anchors. No can see nuffin forever. :P

Ok, so say you get cunning and declare everything as RGBA. You can then ensure you have visible links on focus, by changing the alpha value on the anchor colour and background, but you also have to take care of hover, so that means more alpha transitions there too. Also, when someone is using keyboard access they want things instantly, not with whoopsy Venetian blinds, so any transition on focus will conflict with your hover transition. Also, what happens off hover or off focus? Linkys disappear again. Yay!

There is probably a workaround if you try hard enough, and if your CSS doesn't preclude it by nature (ie: no images) but it'll only be a straight fade in. Can't do roll-ups.

So using visibility: hidden is a no-no for a11y?
Thats what I am using though, from visibility: hidden and opacity:0 -> visibility: visible and opacity: 1 upon hovering. That gives a nice fade effect.

Bloc

Sorry, a little unclear on the z-index..what I meant was that I have a z-index on the avatars already(it has a online/offline icon tucked partly under it lol) - and the dropdown was inside that whole container, so it would NOT go over the next avatar. Which led to the z-index trouble.

Antechinus

Oh I can handle z-index easily enough. That's not the problem (although it can be a little tricky at times).

Actually, there is one way you could get away with opacity tricks: by not having any visible background on the ul or li, and having all visuals in the droppy handled by the anchors. That could work, because then you could animate opacity directly on the anchors.

Other than that, no. Visibility and opacity affect the parent and all children, as I'm sure you know. This is no worries if using hover activation because you can haul up the ul opacity via css by targeting downwards from the parent li, but it totally screws you if someone is using the keyboard (Tab key) to access the links, because you can only select down from the anchor, not back up the other way. This means no can see nuffin forever (I believe I did mention that :D).

Bloc

Yeah, you did. A little slow here haha.

Ok, so I need to correct this in some way. Need to read more about keyboard access and a11y in general I guess.

Antechinus

Yeah I've given menus a bit of thought. Doesn't much matter for the average custom theme, as long as the site offers alternative themes, but I was going over things for default coding. Having figured some of it out (meaning figured some out and read other people's stuff too) I'm sorta reluctant to go back to less functional menus.

What I really want a menu to do is:

1/ display: none all submenu links by default - this means anyone using keyboard access (or a screen reader) can just skip along the top level links, without having to tab through every link in the system. Bear in mind that some people have to tab through things by very awkward methods.

2/ Obviously, they also have to be able to open any submenu by hitting "Enter", after which they can tab through the submenu.

3/ Also has to work via mouse. That can be either a/hover or b/click activation of the submenus.

4/ If using hover activation, really should have a mouseout delay of 500-700 ms for people who need it.

You can get 2, 3/a and 4 with just CSS, but you can't get 1. 1 needs js.

Then there's one of my pet hates when dealing with multi level hover menus in areas like admin. Sooner or later you will accidentally open two or more of the wrong menus (just because you weren't paying attention to exactly where your cursor was) which means you then have a clusterf@ck, and have to wait until the mouseout delay expires before it will clear itself up.

No good saying you can just cut the mouseout delay to some insignificant value, because that will make the system almost unusable for a surprising number of people. It has to stay in the 500-700 ms range.

The only way you can close such a clusterf@ck quickly is if you have javascript, with click-anywhere-to-close functionality. So put that together with 1/ from the first list, and you're getting a good argument for some use of js, IMO. Also, because of said clusterf@cks, I have come to prefer click activation of submenus. They always open exactly when I want them to, and never when I don't want them to. Mouseout delay is effectively infinite too, making them easy to use for anyone with bad tracking skills.

Then there's the famous "mobile first" mindset us enlightened beings are supposed to have these days. That means at least half of your target audience will be on some sort of touchscreen anyway, so hover activation makes no sense for them either.

Put that lot together and I think that if only providing one menu system, it should use some javascript and it should use click activation of submenus. I've tried using pure CSS menus umpteen different ways, but it always falls short in one way or another, and I can't see any way around that.

Bloc

I see your point, but it just feels wrong to impair the users that do have normal needs and use hover menus without problems...I feel I need to work towards catering for both/all, else it won't matter. The equivalent of mobile-first I guess, where mobile-first often means just use the same for desktop users too. True, a lot of people need help..and touch devices really come into play all on its own..but theres still a great portion that do not. Or use both. I know I need the pages to easily scale without looking funny lol, and maybe my hands will shake soon too..but right now I don't. How to adress all scenarios is important.

So I am very reluctant to impose a click-to-drop on whats IMO is a better experience by hovering. But of course, for the same reason not offering ONLY that either.  I have no definitive answers now, and maybe JS is the only way to make sure all works correctly..but I will try to minimize the use of JS, if CSS can do the same job..

Bloc

You know, maybe instead of just offering a one-size-fits-all, one could easily make 2 or 3 different scenarios - th markup would not have to be different(or of it did, using PHP to separate)and let the user decide? If my hands are shaking I press the <relevant-icon-symbol-here>(preferably big enough to hit lol) and off you go. But if I don't need it, press the defautl one..or something like that.

It might be too hard to get to nirvana by only a single staircase. :)

Antechinus

Oh sure, I was saying if providing one menu, click activation with javascript makes the most sense. If you want to provide multiple options you can do that too. However, even though my tracking and reactions are still pretty good, and I can use the default 2.0.x system without significant problems, I have still come to prefer well-behaved click menus over trying to make hover menus behave properly. I actually like them better, so I don't regard them as impairing the experience of normal people at all. Quite the opposite in fact. Frankly I think the people who complain about them are just having a knee jerk reaction and haven't tried using them for long, if at all. This means I have little motivation for coding multiple menu systems.

However, if you wanted to do that, the easiest way would be to provide an option to simply switch off the js and fall back to rocket fast and twitchy hover menus. The js menus should have full CSS fallback anyway, IMO, since I am also concerned about usability with js disabled.

Bloc

Thats ok, everyone is different lol. :) I prefer fast hovering, others prefer clicking to get to it. Having the difference though, between a needed feature and a preferred feature, could be solved by forcibly opting out when it is needed - and offering a user choice when it isn't.  I'll think I try work towards that. :)

Antechinus

Ok, well personally I've tried hover menus so many ways that I'm over them, and no longer regard them as interesting. I've evaluated them and, all things considered, bearing in mind that everything has its trade-offs, I'm of the opinion that hover drops are an inferior option. If I'm coding for my own enjoyment, I'll just code what I want to code. I'm putting so much thought into other aspects of this beast that I'm not inclined to waste time (from my perspective) in offering different menu systems for every possible preference. I'd rather just make sure that whatever I do provide works well. This beast is largely to give people ideas about CSS and markup anyway. So if there's something they don't like, they can code something dfferent. :)

Bloc

Noted. :) (and if you feel this particular sub-discussion is a bit off-topic, feel free to split it up.)

Antechinus

No need. It's all good. We're trying to get people thinking about theming stuff anyway. TBH I probably will provide a no-js option on the menu in some form, since it will have to have fallback to pure CSS for a11y, but I wont be inclined to spend a lot of time on it, and the "primary functionality" (IMO) will be the major consideration.

One thing I do like doing with CSS though, is setting a transition on the top level menu buttons. If they just have a basic hover declaration, they will flash whenever your cursor crosses over them, even if you don't want anything from the menu. I give them a very short transition, which stops them flashing when you don't want them, but is fast enough to "instantly" light up the hover styling if you are aiming the cursor at the button (basically, you have to decelerate your cursor, and in the time it takes you to do that the transition will have expired).

It's not needed on second or third levels, only on the first level. It only has to be very brief, but IMO it gives a much more polished feel. I found ease-in-out worked better than linear, because it gives just a little more time before the transition becomes visible, without increasing overall time.

.linklevel1.active,
/* The hover effects */
.linklevel1:hover, .linklevel1:focus, .listlevel1:hover>.linklevel1 {
background: #222;
color: #cfc2a8;
text-decoration: none;
border: 1px solid #333;
border-radius: 4px;
transition: 0.1s ease-in-out 0.05s;
}
.linklevel1.active:hover, .linklevel1.active:focus {
background: #333;
color: #ffefcf;
border: 1px solid #645441;
transition: 0.1s ease-in-out 0.05s;
}

Antechinus

#39
Hey while we're on about menus and eye candy stuff, there are some things I've tried with the max-height transition roll-up thing (dunno if you've tried them yet).

I tried it with visible overflow on the ul, and with a similar transition on the anchor heights (set for focus and hover). That works for multi-level menus, if you're only concerned with hover activation, and assuming you have no borders, paddings or margins on your li's. The whole thing rolls out very nicely like that.

It's a bit funny with keyboard activation, because each disembodied anchor rolls down out of nowhere, which looks rather odd. Still, if you just want it for a specifically hover-activated option, it's fine.

ETA: Ha! Should have thought of this before too. Ok, say you (meaning you, not necessarily me :D) want your nifty hover droppies, but you want to provide decent a11y for people with poor tracking skillz (like us old sods in a couple more decades :D) so you're going to give said people a nice, long mouseout delay.

This is going to be very annoying at times (ergo my earlier comments about clusterf@cks). So, all you have to do is include a very small js routine to allow instant closing of the submenus if you click outside them.

This is now not a "pure CSS menu", but TBH purist approaches are somewhat like religious fundamentalism, which isn't really my cup of tea. Javascript was invented to control behaviour, and if sanely employed is very good for that purpose. So, we should use it. :)

Advertisement: