I know people out there are doing this, because I click around on their websites all the time. But could I find a tutorial or code snippet? Not a chance! Here’s a first attempt. If anyone spots anything just awful, I’m eager to hear from you.
What is “this”?
Taking a typical CSS-based dropdown navigation (usually built around li:hover rules) and making it friendly for mobile.
While some mobile browsers (iOS implementation in particular) will try to allow you to “hover” with a quick tap, it’s kind of a fiddly experience. And Android doesn’t seem to have this at all. So what’s a guy to do? I’m confident there is better CSS out there, perhaps using focus or some other sort of state-driven styling, but I couldn’t find it. So I resorted to JavaScript. Since jQuery’s already on the site and is right in my wheelhouse, I went where the comfort was. Not saying this is where the performance is, but for such a trivial bit of code, I’m not bothered.
The HTML
You’ve seen these a million times. Navigation created by unordered list (note, intentionally not using <nav> element):
<ul class="nav">
<li><a href="/somelink">Some Link</a></li>
<li><a href="/otherLink">Other Link</a>
<ul>
<li><a href="#">Submenu 1</a></li>
<li><a href="#">Submenu 2</a></li>
</ul>
</li>
</ul>
The CSS
Again, you’ve seen these a bunch of times. A good resource continues to be CSS Wizardry’s Tutorial. Suffice it to say, it boils down to something like this (there’s not much point copying the CSS over, but here’s the important bit):
#nav li:hover ul{ /* Display the dropdown on hover */
left:0; /* Bring back on-screen when needed */
}
Rather than shifting position, yours may have a rule that changes display:none to display:block or somesuch. The key here is that it’s a hover based rule. Mobile browsers do not have hover. For lack of knowledge of any CSS that could remedy the situation, I had to turn to JavaScript.
JavaScript
The theory was this:
- Figure out if the browser is mobile
- If the browser is mobile, convert the top-level nav item to a toggle (show or hide the submenu)
- On toggle, add or remove a special class which will help identify the current state as well as provide visual feedback (could be plus/minus or up/down arrow symbols).
/* site namespace */
var ff = {};
ff.touchEnabled = false;
(function() {
try {
document.createEvent('TouchEvent');
ff.touchEnabled = true;
} catch(e) {}
})();
$(document).ready(function() {
/* convert drop-down to use clicks if touch-enabled */
if (ff.touchEnabled) {
$('.nav').on('click', '#mainnav > li', function(e) {
e.preventDefault();
$this = $(this);
$this.toggleClass('expanded');
if($this.hasClass('expanded')) {
$this.find('ul').show();
} else {
$this.find('ul').hide();
}
});
}
});
The problem that presented itself was this: the top-level link is no longer follow-able. Sometimes these things are obvious before you even write a line of code, but I missed this one. So there were a few options:
- Add toggle widgets that would be tappable, instead of the menu item itself.
- Capture first tap, and then make the link follow-able on the second tap (to close, tap outside of dropdown)
- Move the link out of the top level and make it an option in the sub
- Same as previous, but do it dynamically with .clone() only when touch-enabled devices detected
I was strongly tempted to go with this last option. However, I believe that if the mobile experience is approximating the desktop experience (as opposed to providing a whole different kind of navigation), they should be as close to one another as possible. For that reason, I opted to move the link into the list of dropdown options for both experiences.
Conclusion
The CSS was updated to remove the pointer cursor style on menu items that have a submenu, and the HTML was updated to move the links. The JavaScript now produces the expected results in this new context.
I am under no illusion that this was “ideal,” and I will continue to research alternatives. But in the meantime, if this proves helpful to anybody, fantastic!