Blog: Tutorials, Tips & Tools

How to deal with :hover on touch screen devices

Announcement
Prowebdesign will be doing some webinars and/or tutorials on Responsive Web Design for large interfaces. This year we’ve worked on quite a bit of large responsive UIs. Some of them were for e-commerce sites, some for online booking and travel sites. We have decided to organize our know-how into a couple of online courses. Starting dates will be announced later (1-1,5 month from now). But we would like you to tell us what form of learning you prefer. Please review course content & pricing, and answer our poll questions here.

This article is a small case study about dealing with hover effects on touch screens.

As you know, :hover behavior doesn’t exist on touch screen devices. So when you design a responsive website, you should carefully plan when and where to use :hover interactions. Simple links that open some URL will loose their :hover effect on some touch screen devices. On iOS :hover is triggered before the click event, so you will see the hover style for a brief moment before the page changes. Those are minor things, they don’t affect the functionality of the site. The real problem is a :hover  that either hides or shows another element using display or visibility CSS properties. This type of :hover will transform into the double tap behavior on touch screens.

While coding prowebdesign.ro I ran into double tap behavior twice: with pure CSS main menu drop-downs, and with portfolio sorting filter drop-downs (see Filter 1 and Filter 2 on this page).

Problem was eliminated completely on all devices with screen width less or equal to 768 px. On those main menu is a collapsible toggle-menu, which expands on click. This is probably the best solution available to web designers so far. Toggle-menus are great for user experience on mobiles. They take up almost no space, eliminate :hover interactions, and can hold a large number of links. As for the portfolio filters, I decided not to display them at all on devices with small screens.

But what about touch screen devices with larger resolutions, like tablets?

First of all, you need to decide what variant of layout you will want for those devices. Mobile or more desktop-like. Discussing this in detail is beyond the scope of this article. It depends on many things: from site visitor statistics to your personal preferences. Me, I prefer to see more desktop-like versions of sites on iPad. So I have decided to code prowebdesign.ro accordingly. And this meant that for touch screens from 768 px up :hover generated double tap was there and needed attention.

To recap: at resolutions larger than 768px both main menu and portfolio filters on my site have drop-downs which are displayed on :hover. So on iPad I had to tap once on parent menu link to display the drop-down, and then tap the second time on some link in the drop-down to go to a new page. I am sure this sounds familiar to any tablet user. This way of interacting with menu seems fairly decent to me. Especially since clicking on another parent menu item will hide whatever drop-down menu was already open. This way we get almost exactly the type of interaction that happens if developer decides to use JavaScript click() method instead of CSS :hover to display drop-down menus. The annoying difference is that with properly-written JavaScript click() drop-downs will close if you click on the neighbor link, a link in drop-down, or ANYWHERE ELSE on the page. But with double tap the last drop-down displayed will hang on until you click a link in it and go to another URL. If you don’t click any links, it’ll just hang there.

With filter drop-downs this sticky behavior was especially annoying. Since filter links do not actually point to an URL ( they have href=”#”), the filter drop-down doesn’t disappear if you click on the link inside it, or even on the parent link. Click runs JavaScript, which performs the sorting, but filter drop-down just hangs there obstructing the content.

I’ve put together a starter template. If you look at it on a touch screen device, you will see the behaviors I am speaking about. Template has a simple pure CSS menu with drop-downs, and also mark-up for filters, like ones on my portfolio. Also, here’s a  final template that has all the JavaScript and style tweaks in place. If you try final template on touch screen device, you will have no drop-downs for the main menu, and filters’ drop-downs will not be as sticky as in starter template. You can also download the source files.

I have decided to do my research and think up better options of interacting with both main menu and filters on touch screen for prowebdesign.ro. And since menu and filters cases are a bit different, we will discuss them separately.

Possible solutions for the double tap problem in navigation menus with drop-down sub-menus:

  1. One solution is to eliminate :hovers that hide or show drop-downs using display or visibility altogether, even from desktop site version. This approach has enough advocates and is used by some major frameworks like Twitter Bootstrap. If you go to this page of Bootstrap demo, you will see examples of navbar and tabs – both displaying drop-down on CLICK. This is a viable solution and you can use it. I personally don’t like it for 2 reasons:
    • I prefer to use CSS interactions instead of JavaScript wherever possible.
    • I don’t like the idea of making desktop user click twice to go to some page instead of just hover-and-click. Whatever happened to get anywhere in one click usability tip? :)
  2. Second solution is to leave the :hovers as is and do nothing. If the only place where drop-downs are displayed on :hover is the menu / navbar, then user experience on touch screens is decent enough. But not optimal – see the “sticky drop-down” problem I described above.
  3. Third solution is to write smart-ass piece of JavaScript, which will detect touch devices and turn :hover interactions into click interactions. I am sure that for a good JS coder it’s a piece of cake. But as soon as I ran into the necessity to prevent default action for click event of the parent menu items, I abandoned this path. Because I am a devoted fan of fast no-brainer solutions :).
  4. Fourth solution is to display drop-downs only for devices that support :hover interactions. This solution is particularly good for menus / navbars, but only if you have SUB-MENUS on each sub-section page! If you don’t, you will simply hide parts of your site from touch screen users.
  5. Use popular solution – the toggle menus. *You can download a free responsive template with toggle menu here.

After weighing my options for the menu, I decided to go with solution #4. I do have sub-menus for each section of the site, so it seemed like an optimal path. And, again, I am a devoted fan of fast no-brainer solutions :) Still no-brainer as it is, this solution requires a small JavaScript snippet and couple of tweaks in the styles.

A very common approach is switching between touch-optimized and regular layouts depending on the device’s size. This is easily achieved with media queries. Needless to say, this is wrong. The only thing you can take care of this way is the size, position and visibility of website elements, but you CAN NOT adjust user experience for touch interactions. At the time this article is written, there’s no way to probe for touch screens with media queries only. Handheld media type doesn’t work. Working draft Media Queries 4 with hover was released in 2012 with a promise of blessed land – hover media query! So we wait:). But so far you can not rely on media queries to probe for touch or hover support, you need to use JavaScript.

There are 2 simple ways to check for touch support with JavaScript:

  1. You can use Modernizr.js library. Latest versions of Modernizr (2.6.2 for sure) automatically checks for touch support and attaches a .no-touch class to HTML tag, if device doesn’t support touch.
  2. If you do not want to use Modernizr, you can add small piece of JavaScript below, which will do exactly the same thing.
//test for touch events support and if not supported, attach .no-touch class to the HTML tag.

if (!("ontouchstart" in document.documentElement)) {
document.documentElement.className += " no-touch";
}

© Oscar Godson

In final template those lines of code are there, but commented out, because I used Modernizr anyway.

Now lets use .no-touch class to make menu show drop-downs only for devices with :hover support – as planned.

Whenever you code a pure CSS drop-down menu, you usually have something like this to position drop-downs, make them invisible, and then display them on :hover

.main-menu ul,
.filters ul {
	position: absolute;
	display:none;
	top: 1.5em;
	left: -40px;
	width: 15em; /* left offset of submenus need to match (see below) */
}
.main-menu li:hover ul,
.filters li:hover ul{
	z-index: 99;
	display: block;
}

We will be using the same code to create drop-downs for both menu and filters, that’s why each declaration has 2 selectors – .main-menu and .filters.

To make drop-downs to be displayed only on devices that support :hover, and to be hidden on touch screens, simply add .no-touch before :hover selectors. Like so:

.no-touch .main-menu li:hover ul,
.no-touch .filters li:hover ul{
	z-index: 99;
	display: block;
}

And that’s it! If you are working on starter template, you can now test it on touch screen device and see that neither menu, nor filters have drop-downs. Now we move forward and take care of filters.

Possible solutions to double tap problem on single lists with drop-downs.

  1. One solution is not to display filters at all if they are not crucial for the user experience. But I think that for designer’s portfolio they kind of are.
  2. Second solution is to make unordered lists turn into select element on touch screens. There’s a famous and widely used script for this: Matt Kersley’s responsive select menu. I used it a lot a while back, but abandoned it in favor of toggle-menus. Responsive select script has a major draw-back. If you inspect your code with any kind of dev tools, you will see that you actually have TWO menus floating around: unordered list and select. And they are alternatively displayed for device groups. Redundant!
  3. Third option: to have display filter lists as clickable toggle-menus for ANY size of touch screen devices. Not a bad option. But for the sake of aesthetics and for all tablet users out there, I wanted my desktop look!
  4. Keep desktop look, keep filter drop-downs. Work hard to make them more user-friendly and less sticky:).

I have chosen option #4. **

** Update: after a while I decided to switch to select menus on my site. But you can see the case I refer to below on the demo pages.

What I had in mind is to make filter lists’ behavior to resemble Twitter Bootstrap click drop-downs as much as possible on touch screen devices. Thus, the plan was to:

  • Make drop-downs appear on tap.
  • Make first filter drop-down disappear when second filter is tapped and vice-versa.
  • Make filter drop-down disappear when any link inside it is tapped, including parent link.
  • Make filter drop-down disappear when user taps ANYWHERE else on the screen.

Naturally, this called for JavaScript. Task zero actually was to make filter drop-downs to appear at all, since we’ve completely hidden them on touch screens by adding .no-touch to the CSS.

And here’s the script which makes everything of the above happen:

<script>// <![CDATA[
jQuery(document).ready(function($) { 	//make filters hover behavior switch to tap/clcik on touch screens 	if (!$('html').hasClass('no-touch')) { //Execute code only on a touch screen device 		//Show #filter1 drop-down and hide #filter2 drop-down if it was open 		$('#filter1').bind('touchstart', function(e) { 			$("#filter1 ul.children").toggle(); 			$("#filter2 ul.children").css('display','none'); 			e.stopPropagation(); //Make all touch events stop at the #filter1 container element 		}); 		//Show #filter2 drop-down and hide #filter1 drop-down if it was open 		$('#filter2').bind('touchstart', function(e) { 			$("#filter2 ul.children").toggle(); 			$("#filter1 ul.children").css('display','none'); 			e.stopPropagation(); //Make all touch events stop at the #filter2 container element 		}); 		$(document).bind('touchstart', function(e) { 				$(".filters ul.children").fadeOut(300); //Close filters drop-downs if user taps ANYWHERE in the page 		}); 		$('.filters ul.children').bind('touchstart', function(event){ 				event.stopPropagation(); //Make all touch events stop at the #filter1 ul.children container element 		}); 		$(".filters ul.children a").click(function () { 			  $(".filters ul.children").fadeOut(300); //Close filters drop-downs if user taps on any link in drop-down 		}); 	} });
// ]]></script>

The script is well commented, but I will quickly run through it and explain what happens.

Everything is enclosed in a regular jQuery(document).ready(function($){} function. Also, everything is wrapped into an if (!$('html').hasClass('no-touch')){} statement, that makes code run ONLY on touch screens. Observe how the condition is for HTML element to have a .no-touch class in order the code to run.

Code inside the if() brackets begins with 2 straight forward function(e) which

  • Make drop-downs appear on tap.
  • Make first filter drop-down disappear when second filter is tapped and vice-versa.

The only thing worth mentioning here is the e.stopPropagation(); line. What it does is “hides” the fact that a filter container was tapped from the rest of the document. The reason we need to “hide” it is this function on the next lines:

		$(document).bind('touchstart', function(e) {
				$(".filters ul.children").fadeOut(300); //Close filters drop-downs if user taps ANYWHERE in the page
		});

It makes drop-downs disappear if user taps ANYWHERE on the screen. ANYWHERE naturally includes the filters themselves. But we don’t want drop-downs to disappear when user taps on filters, we want them to appear! That’s why we “hide” or “exclude” taps on filters from the ANYWHERE rule.

We need to make the same “exception” also for tapping on the ul.children containers. And this what the next function is doing:

		$('.filters ul.children').bind('touchstart', function(event){
				event.stopPropagation(); //Make all touch events stop at the .filters ul.children container element
		});

Last thing to do is to close drop-downs when user taps on any of the links inside. Solved with the next function:

		$(".filters ul.children a").click(function () {
			  $(".filters ul.children").fadeOut(300); //Close filters drop-downs if user taps on any link in drop-down
		});

I made drop-down close with a slight fade out effect, so it is less abrupt.
And that’s it.

Here are to more great articles about :hovers on touch screens. They will help you decide what path to follow in your particular case.

iOS has a :hover problem
Are Hover Events Extinct?