Disable hover effects on mobile browsers

Joe White picture Joe White · Nov 28, 2011 · Viewed 106.5k times · Source

I'm writing a Web site that's meant to be used from both desktops and tablets. When it's being visited from a desktop, I want the clickable areas of the screen to light up with :hover effects (different background color, etc.) With a tablet, there's no mouse, so I don't want any hover effects.

The problem is, when I tap something on the tablet, the browser evidently has some kind of "invisible mouse cursor" that it moves to the location I tapped, and then leaves it there -- so the thing I just tapped lights up with a hover effect until I tap something else.

How can I get the hover effects when I'm using the mouse, but suppress them when I'm using the touchscreen?

In case someone was thinking of suggesting it, I don't want to use user-agent sniffing. The same device could have both a touchscreen and a mouse (maybe not so common today, but much more so in the future). I'm not interested in the device, I'm interested in how it's currently being used: mouse or touchscreen.

I already tried hooking the touchstart, touchmove, and touchend events and calling preventDefault() on all of them, which does suppress the "invisible mouse cursor" some of the time; but if I tap rapidly back and forth between two different elements, after a few taps it will start moving the "mouse cursor" and lighting up the hover effects anyway -- it's like my preventDefault isn't always honored. I won't bore you with the details unless necessary -- I'm not even sure that's the right approach to take; if anyone has a simpler fix, I'm all ears.


Edit: This can be reproduced with bog-standard CSS :hover, but here's a quick repro for reference.

<style>
  .box { border: 1px solid black; width: 150px; height: 150px; }
  .box:hover { background: blue; }
</style>
<div class="box"></div>
<div class="box"></div>

If you mouse over either of the boxes, it will get a blue background, which I want. But if you tap on either of the boxes, it will also get a blue background, which is the thing I'm trying to prevent.

I've also posted a sample here that does the above and also hooks jQuery's mouse events. You can use it to see that tap events will also fire mouseenter, mousemove and mouseleave.

Answer

MacFreek picture MacFreek · Mar 17, 2014

I take it from your question that your hover effect changes the content of your page. In that case, my advice is to:

  • Add hover effects on touchstart and mouseenter.
  • Remove hover effects on mouseleave, touchmove and click.

Alternatively, you can edit your page that there is no content change.

Background

In order to simulate a mouse, browsers such as Webkit mobile fire the following events if a user touches and releases a finger on touch screen (like iPad) (source: Touch And Mouse on html5rocks.com):

  1. touchstart
  2. touchmove
  3. touchend
  4. 300ms delay, where the browser makes sure this is a single tap, not a double tap
  5. mouseover
  6. mouseenter
    • Note: If a mouseover, mouseenter or mousemove event changes the page content, the following events are never fired.
  7. mousemove
  8. mousedown
  9. mouseup
  10. click

It does not seem possible to simply tell the webbrowser to skip the mouse events.

What's worse, if a mouseover event changes the page content, the click event is never fired, as explained on Safari Web Content Guide - Handling Events, in particular figure 6.4 in One-Finger Events. What exactly a "content change" is, will depend on browser and version. I've found that for iOS 7.0, a change in background color is not (or no longer?) a content change.

Solution Explained

To recap:

  • Add hover effects on touchstart and mouseenter.
  • Remove hover effects on mouseleave, touchmove and click.

Note that there is no action on touchend!

This clearly works for mouse events: mouseenter and mouseleave (slightly improved versions of mouseover and mouseout) are fired, and add and remove the hover.

If the user actually clicks a link, the hover effect is also removed. This ensure that it is removed if the user presses the back button in the web browser.

This also works for touch events: on touchstart the hover effect is added. It is '''not''' removed on touchend. It is added again on mouseenter, and since this causes no content changes (it was already added), the click event is also fired, and the link is followed without the need for the user to click again!

The 300ms delay that a browser has between a touchstart event and click is actually put in good use because the hover effect will be shown during this short time.

If the user decides to cancel the click, a move of the finger will do so just as normal. Normally, this is a problem since no mouseleave event is fired, and the hover effect remains in place. Thankfully, this can easily be fixed by removing the hover effect on touchmove.

That's it!

Note that it is possible to remove the 300ms delay, for example using the FastClick library, but this is out of scope for this question.

Alternative Solutions

I've found the following problems with the following alternatives:

  • browser detection: Extremely prone to errors. Assumes that a device has either mouse or touch, while a combination of both will become more and more common when touch displays prolifirate.
  • CSS media detection: The only CSS-only solution I'm aware of. Still prone to errors, and still assumes that a device has either mouse or touch, while both are possible.
  • Emulate the click event in touchend: This will incorrectly follow the link, even if the user only wanted to scroll or zoom, without the intention of actually clicking the link.
  • Use a variable to suppress mouse events: This set a variable in touchend that is used as a if-condition in subsequent mouse events to prevents state changes at that point in time. The variable is reset in the click event. See Walter Roman's answer on this page. This is a decent solution if you really don't want a hover effect on touch interfaces. Unfortunately, this does not work if a touchend is fired for another reason and no click event is fired (e.g. the user scrolled or zoomed), and is subsequently trying to following the link with a mouse (i.e on a device with both mouse and touch interface).

Further Reading