How to prevent sticky hover effects for buttons on touch devices
Solution 1
Since this part of CSS Media Queries Level 4 has now been widely implemented since 2018, you can use this:
@media (hover: hover) {
button:hover {
background-color: blue;
}
}
Or in English: "If the browser supports proper/true/real/non-emulated hovering (e.g. has a mouse-like primary input device), then apply this style when button
s are hovered over."
For browsers that do not have this implemented (or didn't at the time of this original answer), I wrote a polyfill to deal with this. Using it, you can transform the above futuristic CSS into:
html.my-true-hover button:hover {
background-color: blue;
}
(A variation on the .no-touch
technique) And then using some client-side JavaScript from the same polyfill that detects support for hovering, you can toggle the presence of the my-true-hover
class accordingly:
$(document).on('mq4hsChange', function (e) {
$(document.documentElement).toggleClass('my-true-hover', e.trueHover);
});
Solution 2
You can remove the hover state by temporarily removing the link from the DOM. See http://testbug.handcraft.com/ipad.html
In the CSS you have:
:hover {background:red;}
In the JS you have:
function fix()
{
var el = this;
var par = el.parentNode;
var next = el.nextSibling;
par.removeChild(el);
setTimeout(function() {par.insertBefore(el, next);}, 0)
}
And then in your HTML you have:
<a href="#" ontouchend="this.onclick=fix">test</a>
Solution 3
This is a common problem with no perfect solution. Hover behavior is useful with a mouse and mostly detrimental with touch. Compounding the problem are devices which support touch and mouse (simultaneously, no less!) like the Chromebook Pixel and Surface.
The cleanest solution I've found is to only enable hover behavior if the device isn't deemed to support touch input.
var isTouch = !!("ontouchstart" in window) || window.navigator.msMaxTouchPoints > 0;
if( !isTouch ){
// add class which defines hover behavior
}
Granted, you lose hover on devices which may support it. However, sometimes hover impacts more than the link itself, e.g. perhaps you want to show a menu when an element is hovered. This approach allows you to test for the existence of touch and perhaps conditionally attach a different event.
I've tested this on the iPhone, iPad, Chromebook Pixel, Surface, and a variety of Android devices. I can't guarantee that it will work when a generic USB touch input (such as a stylus) is added to the mix.
Solution 4
You can override the hover effect for devices that don't support hover. Like:
.my-thing {
color: #BADA55;
}
.my-thing:hover {
color: hotpink;
}
@media (hover: none) {
.my-thing {
color: #BADA55;
}
}
Tested and verified on iOS 12
Hat tip to https://stackoverflow.com/a/50285058/178959 for pointing this out.
Solution 5
From 2020 You can add hover styles inside media query
@media (hover: hover) and (pointer: fine) {
/* css hover class/style */
}
This media query indicates that styles will work on browsers that not emulate :hover so it will NOT work on touch browsers.
Related videos on Youtube
Chris
Updated on November 11, 2021Comments
-
Chris over 2 years
I have created a carousel with a previous and a next button that are always visible. These buttons have a hover state, they turn blue. On touch devices, like iPad, the hover state is sticky, so the button stays blue after tapping it. I don't want that.
I could add a
no-hover
classontouchend
for each button, and make my CSS like this:button:not(.no-hover):hover { background-color: blue; }
but that's probably quite bad for performance, and doesn't handle devices like the Chromebook Pixel (which has both a touchscreen and a mouse) correctly.I could add a
touch
class to thedocumentElement
and make my CSS like this:html:not(.touch) button:hover { background-color: blue; }
But that also doesn't work right on devices with both touch and a mouse.
What I would prefer is removing the hover state
ontouchend
. But it doesn't seem like that is possible. Focusing another element doesn't remove the hover state. Tapping another element manually does, but I can't seem to trigger that in JavaScript.All the solutions I have found seem imperfect. Is there a perfect solution?
-
dasfdsa over 3 years
-
Chris over 2 yearsThat's a pretty nice solution, @dasfdsa! However, it's not a solution for devices that support both a touchscreen and a mouse.
-
Chris almost 11 yearsBut I would like to maintain the hover effect for mouse users.
-
Chris almost 11 yearsWow! I should have known Sjoerd would have the answer. :) There is one small downside though: after a click with a mouse, the hover effect also disappears. Normally that doesn't happen, so it looks a bit weird. Using ontouchend instead of onclick doesn't work though. :(
-
G-Cyrillus almost 11 years:hover and :active can receive same CSS, it's on :focus you have the problem. Actually, if you set background-color via onfocus, color style remains once focus has gone. you need a reset on onlur as well to restore defaut bg
-
Sjoerd Visscher almost 11 years@Chris Good point, I changed the example to set the onclick handler in the ontouchend event.
-
2540625 over 9 yearsPlease consider adding minimal demonstrative code to your answer. Thanks! stackoverflow.com/help/how-to-answer
-
Sjoerd Visscher over 9 years@janaspage It's right there in the link. I've turned this into a community wiki answer, so if you want to put it here, go right ahead!
-
Darren Cook over 9 years@SjoerdVisscher I've pasted it in. StackOverflow likes the code to be in the answer, as links can go away. (And in this case it required not just click through, but then viewing the source, and working out which bits are the technique in question.)
-
Kevin Borders over 9 yearsUsing
setTimeout
sometimes causes flashing. The code appears to work still without it, so it can be removed. -
morrisbret over 9 yearsThis is the best answer IMO BUT the example is incorrect. It's showing the same hover state for touch and non-touch devices. It should only apply the hover state if
.no-touch
is present on thehtml
tag. Otherwise, this answer gets my stamp of approval. -
dudewad about 9 yearsThe problem is that now that mouse-enabled devices that have touch screens are all over the place, you can't really rely on this method anymore. However I can't see much of another way to do it... this is quite a dilemma
-
Rodney almost 9 years@KevinBorders yes on some devices the time delay between the removal and reinsert of the element can be very noticable. Unfortunately, I found on my android 4.4 device that doing this without setTimeout didn't work.
-
Ethan about 8 years@DarrenCook But is this a good idea to remove the element and re-add it? I guess this will cause 'janking' and conflict with 60fps butter smooth needs of the app.
-
Will Lanni about 8 yearsYou could improve this answer using an on() instead of a click(). By removing the element from the DOM and re-attaching it, I lost my click events. When I rewrote my function with the on, binding to the element, then removing and adding worked. For example: `$('body').on('click', '#elementwithhover',function() { // clone, insertafter, remove }); I'll vote this up if you make that change.
-
Jannik almost 8 yearsAwesome, this worked for me, unlike the community wiki/Darren Cook answer.
-
Anthony Astige almost 8 yearsThis fix, when inserted into my application, appears to work on my Nexus 5 running android 4.4.2 (with 100ms delay) in Chrome but not my iOS iPod touch in Safari
-
Vernard Sloggett almost 8 yearsthe clone true preserves the click event on the new elemenet taking the place of the element with the stuck hover. the original element is being removed from the dom after its cloned.
-
Kevin Lee almost 8 yearsFor your code snippet, the square still remains pink after I touched it. I guess you didn't really solve the problem asked in this question?
-
ragamufin over 7 yearsThanks! For my purposes this seems to be supported in most browser caniuse.com/#search=media%20queries and works brilliantly, thanks!
-
Lorenzo Polidori over 7 yearsThe idea is interesting, but I found it a bit jerky and not widely supported on all touch devices. I would prefer a less invasive solution based on touch support detection and pure css like this stackoverflow.com/a/39787268/885464
-
Lorenzo Polidori over 7 yearsGood answer. This is a similar solution: stackoverflow.com/a/39787268/885464
-
theRyanMark over 7 yearsThis has broken for me in iOS10 for me. Has anyone else had the same issue?
-
CherryDT over 7 yearsYou need to look at caniuse.com/#feat=css-media-interaction instead and you'll see it's not supported in Firefox and IE11 :( so you need the polyfill
-
lowtechsun over 7 yearsThis is the best way I find, plus if you increase the timer to 1000ms you can also cover the long-press, see here. Great stuff!
-
drskullster about 7 yearsDidn't work for me, but this did :
document.body.className = 'ontouchstart' in window ? '' : 'hover';
-
xHocquet almost 7 yearsThis solution does not work. First, you used the wrong method to invoke a an event, you should use .trigger(). Second, doesn't work on mobile safari either way.
-
Kaloyan Stamatov over 6 yearsawesome bro, 10x
-
Jusid over 6 yearsYou need to call hoverTouchUnstick() once from the global ontouchstart event handler. And this solution would work perfectly.
-
Gavin about 6 yearsI suppose one way to do it would be to use both Modernizr as well as a library such as mobile-detect.js to make sure it's either a phone or a tablet.
-
amoebe over 5 yearsIt seems the polyfill is no longer supported (repo is archived) and it requires jQuery...
-
cspolton about 5 yearsDoesn't work on hybrid devices, e.g. Windows touchscreen PCs.
-
Trevor Burnham almost 5 yearsThis is now widely supported in mobile browsers, and works like a charm. I think it should be the accepted answer.
-
phil917 over 4 yearsHours of searching for a fix and this finally provided one. Thanks a million!
-
funkju over 4 yearsThis is the more modern solution, no javascript or DOM manipulating required, has full browser support (except IE) and should be the accepted answer.
-
Shivam about 4 yearsYou are the saviour man thanks a lot this worked perfectly for mobile devices
-
Jensei about 4 yearsDoesn't this also remove hover effect for touchscreen devices with mouse/trackpad?
-
Hmerman6006 about 4 yearsThis is definitely the easiest to implement and best answer. Works in Firefox on Android Mobile and PC. Not sure about dual touch and mouse devices.
-
terrymorse about 4 years@Jensei Possibly. Hover rules are removed if the device matches
@media (hover:none)
, or when the user first touches the screen. You can try it out on the live demo page to be sure. -
Banning about 4 yearsIn the end this did not work for me... my case I would have to clone an entire ES6 which is extended and several event listeners and It just started becoming to complicated to solve such a tiny thing.
-
dasfdsa over 3 yearsalso check: css-tricks.com/…
-
Coderer over 2 yearsThis doesn't actually fix the problem for dual input devices. (I'm writing this from a Surface Book 2.) You can move the mouse/touchpad after pressing a button via touch, but it's a shoddy fix. We really need a pseudoclass that's true for hover-with-a-pointer only.
-
Coderer over 2 yearsThis also does not address dual-mode devices (touchscreen laptops etc).
hover: none
won't be true if the browser has both touch and pointer inputs. -
Coderer over 2 yearsThis does not fix the problem for multi-input devices. The
no-touch
class is applied on my Surface Book 2 (trackpad + touchscreen), but the:hover
rule sticks in your fiddle link. -
Coderer over 2 yearsThis works on touch-only browsers, i.e. touchscreens that do not also have a regular pointer available. It does not fix the problem for e.g. touchscreen laptops.
-
Coderer over 2 yearsThanks, I didn't use the interval-based approach but your information about
preventDefault
at the end was incredibly helpful -- I haven't seen this posted anywhere else! -
Chris over 2 yearsNice solution! Doesn't work for devices that support both touch and mouse though.