Monocle Toggle

Hover / Tap Dropdown Navigation for Touchscreens

Need dropdown navigation with a hover on a Touchscreen?

Or should I say...

Uniform nav behavior for ALL devices, including Touchscreen Laptops.

MonocleToggle is a standalone jQuery plugin that works out of the box with any conventionally marked-up navigation.

Just markup your nav like normal and MonocleToggle will do the rest.

				
<nav>
   <ul>
      <li><a href="#">Parent Link</a>
         <ul>
            <li><a href="#">Nested Link</a></li>
            <li><a href="#">Nested Link</a></li>
            <li><a href="#">Nested Link</a></li>
         </ul>
      </li>
   </ul>
</nav>
				
			

Works with any number of nested ul's.

Usage:

  • Link to jQuery.
  • Link to the plugin, or include it's function in your own script file.
  • Markup your nav like normal.
  • Style the classes ".monocle-hover, .monocle-open" (Both of them) to target your hover states.
				
.monocle-hover a,
.monocle-open a,
a:hover {
	// Hover styles here
}
				
			

Demo:

When you need this:

  • Nav elements are links themselves but also have nested ul's.
  • The "dropdown" nested ul's should open on hover or tap.
  • "Mousing Out" or Tapping outside of the nav should close the dropdown.
  • But most importantly,
  • The navigation should function the same for traditional computers, touchscreen devices, and computers with touchscreen monitors.

So how do we get hover, tap, and click to play nice?

Previous methods have tried to serve up one block of code for touch devices and another block of code for traditional devices.

But eloquently addressed here, http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
a browser cannot truly detect if a touch capable display is present.

So play it safe and assume ALL devices have a touchscreen AND a mouse.

The Next Logical Step... Separate touch and mouse events?

The only problem is, in an attempt to remain forward and backward compatible, browsers choose to fire every possible event they can think of.

Open this link on a touchscreen laptop or a computer with a touchscreen monitor and you will see how browsers blur the line between the two.

An example being, mousedown will not fire touchstart, but touchstart WILL fire mousedown.

So we can't detect a touchscreen, and we can't separate touch and mouse events, Where do we go from here?

The solution is by use of a temporary "I'm opening" state that is replaced by an "I'm now open" state.

The logic follows:

  • Touch or mouseenter a nav element to create the temporary "Opening" state and reveal the nested ul.
  • Prevent click-throughs to the link on the parent nav element while in this "Opening" state.
  • After sufficient time has passed for the initial touch event to complete, replace the "Opening" state with a "Now open" state that will allow click-throughs to occur on the parent nav element.
  • Touch outside or mousemove off of the dropdown to close it and remove the "Now open" state.

The end result being, a dropdown nav that will open on hover or touch and will take you to the parent nav element's destination with a single click.

Drawbacks/Devil's advocate:

The only drawback to this approach is speed. If you are quick enough with your mouse, you can have an intended click-through on a parent nav element prevented if you do so before the "I'm opening" state has been removed.

This is unavoidable until such time that browsers decide to handle a tap and a click as two different events.

So you have to leverage the advantage of having the nav behave uniformly for all devices against the possibility of a would-be "Quick Draw McGraw" with a mouse.

I've tried to be as concise as possible, but if you are interested in the how and why, click here

If this article saves you some development woes, or you just like the plugin.

Feel free to donate to it's upkeep

My smiling face
About Dave Carney

I'm a senior Front-end developer at Vehicle Media

I love coding and finding a better way.

I've benefitted from the generosity of others, and I am trying to do my part to pitch in.

If you have any questions or comments, you can reach me at dave@davecarney.net

Big thanks to Osvaldas Valutis
for starting me on the right path.

His plugin doubleTapToGo was the building block for MonocleToggle.

Breaking down MonocleToggle
What's happening in the browser?

In the presence of both a mouse and a touchscreen, when a touch event occurs, most browsers immediately move the cursor to the point of contact. The exception being Edge. It only hides it.

So click, touchstart, onpointerdown, mouseover, and mouseenter all happen simultaneously during a touch event.

What this means is, we can't simply watch for the cursor to be over the nav to allow click-throughs. You would touch the nav to open the dropdown, the browser would see that the cursor is over the nav, and you would be taken to the link, when all you were trying to do was open the dropdown.

Hence the need for the "I'm opening" state. Set that state to last for 500ms, so the initial touch event can complete.

And you guessed it, Edge is 200ms slower than all other browsers. The other accomodation we have to make for Edge is that it only hides the cursor. So following a touch event, Edge will recognize the cursor as being in two places at the same time.

What this means is, we can't rely on mouseover to keep the dropdown open, or rely on mouseleave to close the dropdown. That also goes for jQuery's .hover() event because it uses mouseleave as it's listener as well.

What we have to do is watch for mousemove outside of the nav.

Now that we know what the browser is doing, we can put our logic in order

  1. Open the dropdown
    • Watch for click or mouseenter to fire the "I'm opening" state and reveal the nested ul.
  2. Stop the click-through
    • Have preventDefault() set on any $('nav li:has(ul)');
    • After the "I'm opening" state has expired, swap it out with the "I'm open" state.
    • Remove preventDefault() from the $('nav li:has(ul);
  3. Close the dropdown
    • Watch for a touch outside of the nav, or a mousemove outside of the nav to close the dropdown.
    • Mousemove is the only listener you can rely on, because of Edge's double cursor behavior. The cursor, is indeed, not over the nav but is not moving elsewhere on the page, so the nav will remain open until such time.

Not mentioned in that logic flow are checks in place to make sure you don't have two sibling dropdowns open at the same time.

MonocleToggle natively uses .children('ul') to show the nested ul's incrementally as you hover downward. In the event that you need a 2nd tier ul to appear in it's open state, simply change it to use .find('ul')

Larger implications

Edge's double cursor behavior extends to any element on the page that has hidden content.

You cannot rely on traditional hover techniques to reveal them.

But with a grasp of the logic being used here, you can create similar listeners to achieve the same effect.

Don't be surprised that because of one browser, you have to triple the amount of code needed to pull it off.