179

I want to ignore all :hover CSS declarations if a user visits our website via touch device. Because the :hover CSS does not make sense, and it can even be disturbing if a tablet triggers it on click/tap because then it might stick until the element loses focus. To be honest, I don't know why touch devices feel the need to trigger :hover in first place - but this is reality, so this problem is reality as well.

a:hover {
   color:blue;
   border-color:green;
   // etc. > ignore all at once for touch devices
}

So, (how) can I remove/ignore all CSS :hover declarations at once (without having to know each one) for touch devices after having them declared?

Simon Ferndriger
  • 3,580
  • 6
  • 23
  • 47
  • This is similar to [How to prevent sticky hover effects for buttons on touch devices](http://stackoverflow.com/questions/17233804/how-to-prevent-sticky-hover-effects-for-buttons-on-touch-devices) - however, more general – Simon Ferndriger May 27 '14 at 09:13
  • 1
    I get around this with a php solution where I discover if the viewing device is mobile or not and use the correct stylesheet with various changes based on that. That doesn't answer your question though. You could potentially use @media queries but that's not guaranteed to work either when phones and tablets have full hd resolutions. – TomFirth May 27 '14 at 10:43
  • 1
    Possible duplicate of [Disable hover effects on mobile browsers](http://stackoverflow.com/questions/8291517/disable-hover-effects-on-mobile-browsers) – rnevius Apr 26 '16 at 19:46

15 Answers15

191

tl;dr use this: https://jsfiddle.net/57tmy8j3/

If you're interested why or what other options there are, read on.

Quick'n'dirty - remove :hover styles using JS

You can remove all the CSS rules containing :hover using Javascript. This has the advantage of not having to touch CSS and being compatible even with older browsers.

function hasTouch() {
  return 'ontouchstart' in document.documentElement
         || navigator.maxTouchPoints > 0
         || navigator.msMaxTouchPoints > 0;
}

if (hasTouch()) { // remove all the :hover stylesheets
  try { // prevent exception on browsers not supporting DOM styleSheets properly
    for (var si in document.styleSheets) {
      var styleSheet = document.styleSheets[si];
      if (!styleSheet.rules) continue;

      for (var ri = styleSheet.rules.length - 1; ri >= 0; ri--) {
        if (!styleSheet.rules[ri].selectorText) continue;

        if (styleSheet.rules[ri].selectorText.match(':hover')) {
          styleSheet.deleteRule(ri);
        }
      }
    }
  } catch (ex) {}
}

Limitations: stylesheets must be hosted on the same domain (that means no CDNs). Disables hovers on mixed mouse & touch devices like Surface or iPad Pro, which hurts the UX.

CSS-only - use media queries

Place all your :hover rules in a @media block:

@media (hover: hover) {
  a:hover { color: blue; }
}

or alternatively, override all your hover rules (compatible with older browsers):

a:hover { color: blue; }

@media (hover: none) {
  a:hover { color: inherit; }
}

Limitations: works only on iOS 9.0+, Chrome for Android or Android 5.0+ when using WebView. hover: hover breaks hover effects on older browsers, hover: none needs overriding all the previously defined CSS rules. Both are incompatible with mixed mouse & touch devices.

The most robust - detect touch via JS and prepend CSS :hover rules

This method needs prepending all the hover rules with body.hasHover. (or a class name of your choice)

body.hasHover a:hover { color: blue; }

The hasHover class may be added using hasTouch() from the first example:

if (!hasTouch()) document.body.className += ' hasHover'

However, this whould have the same drawbacks with mixed touch devices as previous examples, which brings us to the ultimate solution. Enable hover effects whenever a mouse cursor is moved, disable hover effects whenever a touch is detected.

function watchForHover() {
  // lastTouchTime is used for ignoring emulated mousemove events
  let lastTouchTime = 0

  function enableHover() {
    if (new Date() - lastTouchTime < 500) return
    document.body.classList.add('hasHover')
  }

  function disableHover() {
    document.body.classList.remove('hasHover')
  }

  function updateLastTouchTime() {
    lastTouchTime = new Date()
  }

  document.addEventListener('touchstart', updateLastTouchTime, true)
  document.addEventListener('touchstart', disableHover, true)
  document.addEventListener('mousemove', enableHover, true)

  enableHover()
}

watchForHover()

This should work basically in any browser and enables/disables hover styles as needed.

Here's the full example - modern: https://jsfiddle.net/57tmy8j3/
Legacy (for use with old browsers): https://jsfiddle.net/dkz17jc5/19/

blade
  • 8,408
  • 6
  • 31
  • 35
  • Thank you! Just one comment: wouldn't it be better to check first if `document.styleSheets` exists instead of using the `try/catch`? – Simon Ferndriger May 20 '15 at 06:39
  • 1
    I've used `try/catch` just in case some odd error shows up, that could crash the script, like deleteRule not working because of same-origin policy or flaky support on IE (edited the comment to reflect that). But yeah, in most cases, just a simple check should be sufficient. – blade May 20 '15 at 08:15
  • 1
    It's worth mentioning that desktop browsers including Firefox will not results in 'notouch'. They report understanding touch events. – Harry B Nov 10 '15 at 08:23
  • 3
    This solution does not work for mixed devices where hover shall work for the mouse but not for touch events – nbar May 10 '16 at 14:01
  • @nbar thanks for the observation, I've updated the answer – blade Jun 10 '16 at 15:15
  • For me it works but I had to modify the touch condition: `var touch = (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));` – David Novák Aug 02 '16 at 08:55
  • I noticed that this does not affect stylesheets, which were added using `@import url('path/to/stylesheet.css')`. I'm curious about why. – user2939415 Oct 06 '16 at 05:51
  • This solution is not correct. What if you have a selector like this `li, a:hover { border: 1px solid red; }` `selectorText` would be `li, a:hover`. Using this code all `li` loses their border. Only `a:hover` should lose their border. – Haneev Jun 15 '17 at 11:20
  • 2
    @Haneev it is correct for most cases, although not for your specific one. Making your rule work would add a lot of complexity to the code, which would make the answer harder to understand. I would suggest either splitting the rules or using the 2nd solution, which is more robust anyway. However, you're very welcome to suggest an edit, if you can improve the first solution without making it too complicated. – blade Jun 15 '17 at 14:44
  • "remove :hover styles using JS" - didn't work for me, because of "stylesheets must be hosted on the same domain". I've stylesheets on the same domain and also CDN links. Here is the improved code, it works in this case (there is additional check that stylesheets has same domain): ```JS: var styleSheet = document.styleSheets[si]; if (styleSheet && styleSheet.href && ~styleSheet.href.indexOf(location.href) && styleSheet.rules ) { ... } ``` – Alex Golovin May 25 '19 at 13:03
  • @AlexGolovin Since you have stylesheets on same domain and CDN, does your change remove `:hover` styles from both or just from the ones on your own domain? Did you find a solution for removing from the ones on a CDN too? – user779159 May 09 '20 at 08:21
  • @user779159 I found solution which works for me. I have JS function isTouchDevice, if it's returns false, I'm adding class "is-not-touch" to .body, and then using SCSS adding hover styles like this: ```.element-selector { .is-not-touch & { &:hover { background: red; } } }``` In this way hover styles applies only to non touch screens. Make sense? – Alex Golovin May 09 '20 at 12:13
50

Pointer adaptation to the rescue!

Since this hasn't been touched in awhile, you can use:

a:link, a:visited {
   color: red;
}

a:hover {
   color:blue;
}

@media (hover: none) {
   a:link, a:visited {
      color: red;
   }
}

See this demo in both your desktop browser and your phone browser. Supported by modern touch devices.

Note: Keep in mind that since a Surface PC's primary input (capability) is a mouse, it will end up being a blue link, even if it's a detached (tablet) screen. Browsers will (should) always default to the most precise input's capability.

Jason T Featheringham
  • 3,188
  • 23
  • 32
  • Thanks, this would be convenient! However, I tried it out with Chrome in mobile mode, but it didn't work out - when I "tapped" it, it went blue anyway... – Simon Ferndriger Mar 23 '17 at 08:37
  • 1
    @SimonFerndriger That's just a limitation of Chrome devtools. Try opening the link in an actual touch-only device. – Jason T Featheringham Apr 07 '17 at 07:00
  • 1
    Working perfectly on iPad Chrome / Safari vs. OS X Chrome / Safari. Thanks! Finally the elegant solution you'd want for such a small visual issue. – rakaloof Jul 05 '17 at 07:41
  • Indeed! Keep in mind that since a Surface PC's primary input (capability) is a mouse, it will end up being a blue link, even if it's a detached (tablet) screen. Browsers will (should) always default to the more precise capability. – Jason T Featheringham Jul 25 '17 at 07:07
  • 3
    I just tried this on my actual iPhone 6S running iOS 11, and the link went blue. – Lukas Petr Nov 27 '17 at 08:30
  • Strange, my iPhone 7 running iOS 11 does not. – Jason T Featheringham Apr 13 '18 at 01:12
  • 1
    Also tried this with Nexus 5X running Android 8.1.0 and Chrome 68.0.3440.91. The link turned blue. – Trevin Avery Aug 29 '18 at 18:58
  • Didn't work with my Moto G5 running Android 7.0 and Chrome 69 - the link went blue – binaryfunt Sep 21 '18 at 13:18
  • The `:link` CSS pseudo-class represents an element that has not yet been visited, so this answer will behave differently for links that have not yet been visited vs. links that have been visited. Also, I don't understand the logic of this answer. I would expect the selector inside of the media query to be `a:hover` to override the blue, but it's just redeclaring the same CSS that's already at the top (using `a:link`). Also, it's a shame to have to include non-hover styling twice. – Adam Taylor Aug 14 '20 at 02:36
  • @AdamTaylor good catch on `:link`. I've updated to use `:link, :visited`. The reuse of `:link, :visited` instead of `:hover` inside the media query is utilizing the cascade (of selectors of equal specificity), while being explicit that `:hover` should not be respected. This is the same as the LoVeHAte pattern. There are multiple ways to assuage your concerns (using CSS Custom Properties, `@media`-first design, etc.), but this pattern assures this code will work in all browsers, past, present and future. – Jason T Featheringham Aug 19 '20 at 01:50
47

2020 Solution - CSS only - No Javascript

Use media hover with media pointer will help you guys resolve this issue. Tested on chrome Web and android mobile. I known this old question but I didn't find any solution like this.

@media (hover: hover) and (pointer: fine) {
  a:hover { color: red; }
}
<a href="#" >Some Link</a>
Hải Bùi
  • 944
  • 5
  • 13
15

I have encountered the same problem (in my case with Samsung mobile browsers) and therefore I stumbled upon this question.

Thanks to Calsal's answer I found something that I believe will exclude virtually all desktop browsers because it seems to be recognized by the mobile browsers I tried (see screenshot from a compiled table: CSS pointer feature detection table ).

MDN web docs state that

The pointer CSS @media feature can be used to apply styles based on whether the user's primary input mechanism is a pointing device, and if so, how accurate it is

.

What I discovered is that pointer: coarse is something that is unknown to all desktop browsers in the attached table but known to all mobile browsers in the same table. This seems to be most effective choice because all other pointer keyword values give inconsistent results.

Hence you could construct a media query like Calsal described but slightly modified. It makes use of a reversed logic to rule out all touch devices.

Sass mixin:

@mixin hover-supported {    
    /* 
     * https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer 
     * coarse: The primary input mechanism includes a pointing device of limited accuracy.
     */
    @media not all and (pointer: coarse) {
        &:hover {
            @content;
        }
    }
}

a {
    color:green;
    border-color:blue;

    @include hover-supported() {
        color:blue;
        border-color:green;
    }
}

Compiled CSS:

a {
  color: green;
  border-color: blue;
}
@media not all and (pointer: coarse) {
  a:hover {
    color: blue;
    border-color: green;
  }
}

It is also described in this gist I created after researching the problem. Codepen for empirical research.

UPDATE (2018): As of writing this update, 2018-08-23, and pointed out by @DmitriPavlutin this technique no longer seems to work with Firefox desktop.

UPDATE (2021): It has been pointed out to me that it seems to work as of Firefox 87.

ProgrammerPer
  • 1,064
  • 1
  • 10
  • 22
14

According to Jason´s answer we can address only devices that doesn't support hover with pure css media queries. We can also address only devices that support hover, like moogal´s answer in a similar question, with @media not all and (hover: none). It looks weird but it works.

I made a Sass mixin out of this for easier use:

@mixin hover-supported {
    @media not all and (hover: none) {
        &:hover {
            @content;
        }
    }
}

Update 2019-05-15: I recommend this article from Medium that goes through all different devices that we can target with CSS. Basically it's a mix of these media rules, combine them for specific targets:

@media (hover: hover) {
    /* Device that can hover (desktops) */
}
@media (hover: none) {
    /* Device that can not hover with ease */
}
@media (pointer: coarse) {
    /* Device with limited pointing accuracy (touch) */
}
@media (pointer: fine) {
    /* Device with accurate pointing (desktop, stylus-based) */
}
@media (pointer: none) {
    /* Device with no pointing */
}

Example for specific targets:

@media (hover: none) and (pointer: coarse) {
    /* Smartphones and touchscreens */
}

@media (hover: hover) and (pointer: fine) {
    /* Desktops with mouse */
}

I love mixins, this is how I use my hover mixin to only target devices that supports it:

@mixin on-hover {
    @media (hover: hover) and (pointer: fine) {
        &:hover {
            @content;
        }
    }
}

button {
    @include on-hover {
        color: blue;
    }
}
Calsal
  • 751
  • 9
  • 19
  • that doesn't work at all. you have hover: none in the media query for a hover-supported mixin? you need pointer: coarse as per the other answer here. – Allan Nienhuis Aug 03 '18 at 19:41
  • Have you even tried it or do you just say it doesn't work because it looks weird? That's why I wrote "It looks weird".. And it DOES work for me without pointer: coarse. – Calsal Sep 02 '19 at 14:42
  • I had some inconsistent results with the CSS-approach to this on my OnePlus 5T (in both Chrome and Firefix). This worked fine on iOS, but I couldn't get it to work on the OnePlus. I tested thoroughly using the [any-pointer and any-hover](https://googlechrome.github.io/samples/media-hover-pointer/) – Zeth Jul 01 '20 at 14:23
6

I'm dealing with a similar problem currently.

There are two main options that occur to me immediately: (1) user-string checking, or (2) maintaining separate mobile pages using a different URL and having users choose what's better for them.

  1. If you're able to use an internet duct-tape language such as PHP or Ruby, you can check the user string of the device requesting a page, and simply serve the same content but with a <link rel="mobile.css" /> instead of the normal style.

User strings have identifying information about browser, renderer, operating system, etc. It would be up to you to decide what devices are "touch" versus non-touch. You may be able to find this information available somewhere and map it into your system.

A. If you're allowed to ignore old browsers, you just have to add a single rule to the normal, non-mobile css, namely: EDIT: Erk. After doing some experimentation, I discovered the below rule also disables the ability to follow links in webkit-browsers in addition to just causing aesthetic effects to be disabled - see http://jsfiddle.net/3nkcdeao/
As such, you'll have to be a bit more selective as to how you modify rules for the mobile case than what I show here, but it may be a helpful starting point:

* { 
    pointer-events: none !important; /* only use !important if you have to */
}

As a sidenote, disabling pointer-events on a parent and then explicitly enabling them on a child currently causes any hover-effects on the parent to become active again if a child-element enters :hover.
See http://jsfiddle.net/38Lookhp/5/

B. If you're supporting legacy web-renderers, you'll have to do a bit more work along the lines of removing any rules which set special styles during :hover. To save everyone time, you might just want to build an automated copying + seding command which you run on your standard style sheets to create the mobile versions. That would allow you to just write/update the standard code and scrub away any style-rules which use :hover for the mobile version of your pages.

  1. (I) Alternatively, simply make your users aware that you have an m.website.com for mobile devices in addition to your website.com. Though subdomaining is the most common way, you could also have some other predictable modification of a given URL to allow mobile users to access the modified pages. At that stage, you would want to be sure they don't have to modify the URL every time they navigate to another part of the site.

Again here, you may be able to just add an extra rule or two to the stylesheets or be forced to do something slightly more complicated using sed or a similar utility. It would probably be easiest to apply :not to your styling rules like div:not(.disruptive):hover {... wherein you would add class="disruptive" to elements doing annoying things for mobile users using js or the server language, instead of munging the CSS.

  1. (II) You can actually combine the first two and (if you suspect a user has wandered to the wrong version of a page) you can suggest that they switch into/out of the mobile-type display, or simply have a link somewhere which allows users to flop back and forth. As already-stated, @media queries might also be something to look use in determining what's being used to visit.

  2. (III) If you're up for a jQuery solution once you know what devices are "touch" and which aren't, you might find CSS hover not being ignored on touch-screen devices helpful.

Community
  • 1
  • 1
6

try this:

@media (hover:<s>on-demand</s>) {
    button:hover {
        background-color: #color-when-NOT-touch-device;
    }
}

UPDATE: unfortunately W3C has removed this property from the specs (https://github.com/w3c/csswg-drafts/commit/2078b46218f7462735bb0b5107c9a3e84fb4c4b1).

Marouen Mhiri
  • 1,518
  • 1
  • 10
  • 20
5

You can use Modernizr JS (see also this StackOverflow answer), or make a custom JS function:

function is_touch_device() {
 return 'ontouchstart' in window        // works on most browsers 
  || navigator.maxTouchPoints;       // works on IE10/11 and Surface
};

if ( is_touch_device() ) {
  $('html').addClass('touch');
} else {
  $('html').addClass('no-touch');
} 

to detect the support of touch event in the browser, and then assign a regular CSS property, traversing the element with the html.no-touch class, like this:

html.touch a {
    width: 480px;
}

/* FOR THE DESKTOP, SET THE HOVER STATE */
html.no-touch a:hover {
   width: auto;
   color:blue;
   border-color:green;
}
Community
  • 1
  • 1
Giacomo Paita
  • 1,313
  • 2
  • 11
  • 20
  • That was also suggested by @blade - however, using the `html` tag instead of the `body` tag might make it a little better to read. That's actually the solution I am currently using. Thank you anyway. – Simon Ferndriger Jul 28 '16 at 07:57
1

It was helpful for me: link

function hoverTouchUnstick() {
    // Check if the device supports touch events
    if('ontouchstart' in document.documentElement) {
        // Loop through each stylesheet
        for(var sheetI = document.styleSheets.length - 1; sheetI >= 0; sheetI--) {
            var sheet = document.styleSheets[sheetI];
            // Verify if cssRules exists in sheet
            if(sheet.cssRules) {
                // Loop through each rule in sheet
                for(var ruleI = sheet.cssRules.length - 1; ruleI >= 0; ruleI--) {
                    var rule = sheet.cssRules[ruleI];
                    // Verify rule has selector text
                    if(rule.selectorText) {
                        // Replace hover psuedo-class with active psuedo-class
                        rule.selectorText = rule.selectorText.replace(":hover", ":active");
                    }
                }
            }
        }
    }
}
mineroot
  • 1,487
  • 15
  • 22
1

This is also a possible workaround, but you will have to go through your css and add a .no-touch class before your hover styles.

Javascript:

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

CSS Example:

<style>
p span {
    display: none;
}

.no-touch p:hover span {
    display: inline;
}
</style>
<p><a href="/">Tap me</a><span>You tapped!</span></p>

Source

P.s. But we should remember, there are coming more and more touch-devices to the market, which are also supporting mouse input at the same time.

mr.mii
  • 83
  • 1
  • 5
0

This might not be a perfect solution yet (and it’s with jQuery) but maybe it’s a direction / concept to work on: what about doing it the other way round? Which means deactivating the :hover css states by default and activate them if a mousemove event is detected anywhere on the document. Of course this does not work if someone deactivated js. What else might speak against doing it this way round?

Maybe like this:

CSS:

/* will only work if html has class "mousedetected" */
html.mousedetected a:hover {
   color:blue;
   border-color:green;
}

jQuery:

/* adds "mousedetected" class to html element if mouse moves (which should never happen on touch-only devices shouldn’t it?) */
$("body").mousemove( function() {
    $("html").addClass("mousedetected");
});
  • 1
    One problem is actually that it’s also a .mousemove() when clicking on something on touch devices. – lars at upstruct Dec 13 '16 at 12:51
  • 1
    And you probably will trigger this event non-stop, thereby having an impact on performance. If this would work, than you should better use `$('body').one.('mousemove', ...)` – Simon Ferndriger Apr 10 '17 at 07:30
0

Try this (i use background and background-color in this example):

var ClickEventType = ((document.ontouchstart !== null) ? 'click' : 'touchstart');

if (ClickEventType == 'touchstart') {
            $('a').each(function() { // save original..
                var back_color = $(this).css('background-color');
                var background = $(this).css('background');
                $(this).attr('data-back_color', back_color);
                $(this).attr('data-background', background);
            });

            $('a').on('touchend', function(e) { // overwrite with original style..
                var background = $(this).attr('data-background');
                var back_color = $(this).attr('data-back_color');
                if (back_color != undefined) {
                    $(this).css({'background-color': back_color});
                }
                if (background != undefined) {
                    $(this).css({'background': background});
                }
            }).on('touchstart', function(e) { // clear added stlye="" elements..
                $(this).css({'background': '', 'background-color': ''});
            });
}

css:

a {
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
HU TanKy
  • 17
  • 1
  • 5
0

The dirty way... Not elegant, but the easiest way that can save you.

Remove anything that characterizes the hover.

.your-class:hover:before {
  color: blue;
  background: linear-gradient(to bottom, rgba(231,56,39,0) 0%, #aaaaaa 100%);
}

@media all and (min-width:320px) and (max-width: 960px) {
    .your-class:hover:before {
    color: black;
    background: transparent;
  }
}
Fellipe Sanches
  • 4,176
  • 3
  • 20
  • 26
0

If Your issue is when you touch/tap on android and whole div covered by blue transparent color! Then you need to just change the

CURSOR : POINTER; to CURSOR : DEFAULT;

use mediaQuery to hide in mobile phone/Tablet.

This works for me.

-1

Try this easy 2019 jquery solution, although its been around a while;

  1. add this plugin to head:

    src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"

  2. add this to js:

    $("*").on("touchend", function(e) { $(this).focus(); }); //applies to all elements

  3. some suggested variations to this are:

    $(":input, :checkbox,").on("touchend", function(e) {(this).focus);}); //specify elements

    $("*").on("click, touchend", function(e) { $(this).focus(); }); //include click event

    css: body { cursor: pointer; } //touch anywhere to end a focus

Notes

  • place plugin before bootstrap.js to avoif affecting tooltips
  • only tested on iphone XR ios 12.1.12, and ipad 3 ios 9.3.5, using Safari or Chrome.

References:

https://code.jquery.com/ui/

https://api.jquery.com/category/selectors/jquery-selector-extensions/

darren
  • 57
  • 1
  • 2