How do I simulate a hover with a touch in touch enabled browsers?
Solution 1
OK, I've worked it out! It involves changing the CSS slightly and adding some JS.
Using jQuery to make it easy:
$(document).ready(function() {
$('.hover').on('touchstart touchend', function(e) {
e.preventDefault();
$(this).toggleClass('hover_effect');
});
});
In english: when you start or end a touch, turn the class hover_effect
on or off.
Then, in your HTML, add a class hover to anything you want this to work with. In your CSS, replace any instance of:
element:hover {
rule:properties;
}
with
element:hover, element.hover_effect {
rule:properties;
}
And just for added usefulness, add this to your CSS as well:
.hover {
-webkit-user-select: none;
-webkit-touch-callout: none;
}
To stop the browser asking you to copy/save/select the image or whatever.
Easy!
Solution 2
All you need to do is bind touchstart on a parent. Something like this will work:
$('body').on('touchstart', function() {});
You don't need to do anything in the function, leave it empty. This will be enough to get hovers on touch, so a touch behaves more like :hover and less like :active. iOS magic.
Solution 3
Try this:
<script>
document.addEventListener("touchstart", function(){}, true);
</script>
And in your CSS:
element:hover, element:active {
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-user-select: none;
-webkit-touch-callout: none /*only to disable context menu on long press*/
}
With this code you don't need an extra .hover class!
Solution 4
To answer your main question: “How do I simulate a hover with a touch in touch enabled browsers?”
Simply allow ‘clicking’ the element (by tapping the screen), and then trigger the hover
event using JavaScript.
var p = document.getElementsByTagName('p')[0];
p.onclick = function() {
// Trigger the `hover` event on the paragraph
p.onhover.call(p);
};
This should work, as long as there’s a hover
event on your device (even though it normally isn’t used).
Update: I just tested this technique on my iPhone and it seems to work fine. Try it out here: http://jsfiddle.net/mathias/YS7ft/show/light/
If you want to use a ‘long touch’ to trigger hover instead, you can use the above code snippet as a starting point and have fun with timers and stuff ;)
Solution 5
Further Improved Solution
First I went with the Rich Bradshaw's approach, but then problems started to appear. By doing the e.preventDefault() on 'touchstart' event, the page no longer scrolls and, neither the long press is able to fire the options menu nor double click zoom is able to finish executing.
A solution could be finding out which event is being called and just e.preventDefault() in the later one, 'touchend'. Since scroll's 'touchmove' comes before 'touchend' it stays as by default, and 'click' is also prevented since it comes afterwords in the event chain applied to mobile, like so:
// Binding to the '.static_parent' ensuring dynamic ajaxified content
$('.static_parent').on('touchstart touchend', '.link', function (e) {
// If event is 'touchend' then...
if (e.type == 'touchend') {
// Ensuring we event prevent default in all major browsers
e.preventDefault ? e.preventDefault() : e.returnValue = false;
}
// Add class responsible for :hover effect
$(this).toggleClass('hover_effect');
});
But then, when options menu appears, it no longer fires 'touchend' responsible for toggling off the class, and next time the hover behavior will be the other way around, totally mixed up.
A solution then would be, again, conditionally finding out which event we're in, or just doing separate ones, and use addClass() and removeClass() respectively on 'touchstart' and 'touchend', ensuring it always starts and ends by respectively adding and removing instead of conditionally deciding on it. To finish we will also bind the removing callback to the 'focusout' event type, staying responsible for clearing any link's hover class that might stay on and never revisited again, like so:
$('.static_parent').on('touchstart', '.link', function (e) {
$(this).addClass('hover_effect');
});
$('.static_parent').on('touchend focusout', '.link', function (e) {
// Think double click zoom still fails here
e.preventDefault ? e.preventDefault() : e.returnValue = false;
$(this).removeClass('hover_effect');
});
Atention: Some bugs still occur in the two previous solutions and, also think (not tested), double click zoom still fails too.
Tidy and Hopefully Bug Free (not :)) Javascript Solution
Now, for a second, cleaner, tidier and responsive, approach just using javascript (no mix between .hover class and pseudo :hover) and from where you could call directly your ajax behavior on the universal (mobile and desktop) 'click' event, I've found a pretty well answered question from which I finally understood how I could mix touch and mouse events together without several event callbacks inevitably changing each other's ones up the event chain. Here's how:
$('.static_parent').on('touchstart mouseenter', '.link', function (e) {
$(this).addClass('hover_effect');
});
$('.static_parent').on('mouseleave touchmove click', '.link', function (e) {
$(this).removeClass('hover_effect');
// As it's the chain's last event we only prevent it from making HTTP request
if (e.type == 'click') {
e.preventDefault ? e.preventDefault() : e.returnValue = false;
// Ajax behavior here!
}
});
Rich Bradshaw
I'm a web developer from the UK. PHP, HTML5, CSS3 and jQuery-ed javascript are my specialties, thought I also dabble in Python, Bash scripting and SQL when needed.
Updated on March 08, 2022Comments
-
Rich Bradshaw about 2 years
With some HTML like this:
<p>Some Text</p>
Then some CSS like this:
p { color:black; } p:hover { color:red; }
How can I allow a long touch on a touch enabled device to replicate hover? I can change markup/use JS etc, but can't think of an easy way to do this.
-
Rich Bradshaw almost 14 yearsI'm specifically thinking of iPhone OS - it's a demo of CSS animation on which for many it's easy to use hover rather than clicks to start.
-
-
Rich Bradshaw almost 14 yearsI didn't know about that onhover.call(p) bit, that's pretty cool. There's no off hover though...
-
Marcus almost 13 yearsHave you tested this with multiple elements and long touch? Meaning the user moves his finger around the screen, and show a hover style for each element?
-
Kzqai over 12 yearsSeems like if you're using jQuery already, you might be able to just call the hover event with jQuery itself, e.g.: jQuery.hover(); Not sure the api supports that, though.
-
Kzqai over 12 yearsWhoops, that was a comment more applicable to the post above tthat actually uses jQuery, sorry.
-
Jacksonkr almost 11 yearsThis is great. I split the domready function up though because
toggle
will mess things up if the user touches one element and drags to another (eg if they touched the wrong item and are trying to cancel the touch) -
matpol over 10 yearssorry to be pedantic but jquery is javacript
-
Codebeat over 10 years@matpol: jQuery is javascript but javascript is not jQuery. Special for you I add the word 'pure', pure javascript. Satisfied sir?
-
matpol over 10 yearsyou can't use jquery without using 'pure' javascript. I only make the point because some people who frequent SO don't appear to know this.
-
Codebeat over 10 yearsA mix of native Javascript and jQuery, Satisfied?
-
Даниил Пронин almost 10 yearsI removed
touchend
andpreventDefault()
in my website and it works good but now menus doesn't automatically closed by tap out of target. -
Kozy over 9 yearsthis works for me.. some other things that might help this even more is instead of adding javascript, do this: <body ontouchstart=""> then add a small timer in the css :active event: tr:active { background-color:#118aab; transition: all .2s linear; -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-user-select: none; -webkit-touch-callout: none }
-
Darryl Young about 9 yearsI'd also like some advice on how to then close such a menu when the user touches somewhere else or begins to scroll. I guess there could be another
touchstart
listener on the body (or similar) but I wondered if there's a better way. Thanks! -
Roman Losev almost 9 yearsWorked for me. Thanks... The only thing - it lags a bit... I mean - after you press, it doesn't load instantly.
-
samuelkobe over 8 yearsDoes this solution allow for the link to executed on the second tap?
-
myfunkyside over 8 years+1 for the
user-select
andtouch-callout
fixes, 'bout a year ago I was desperately looking for this functionality but couldn't find it anywhere -
Richard Young over 8 yearsDo you need to have any special scripting or call the function? It works for me but only on one page and I don't know what the difference is.
-
Anselm about 8 yearsUse fastclick.js to remove the lag
-
Jack about 8 yearsNo it triggers the click on first touch still. I just tested it.
-
alsobubbly almost 8 yearsIs there anyway to ignore a scroll gesture? This does exactly what I need it to but now I can't scroll up and down the content that has this effect.
-
Petraeus about 7 yearsI removed the preventDefault since I want the scrolling to be available but thanks for the tips on touchstart and touchend! God send this is the only answer that reliably works across all browsers.
-
Hunter Turner over 6 yearsAwesome solution! Quick and easy.
-
Flavien Volken over 6 yearsIs the link content still relevant now?
-
Giorgio Tempesta over 5 yearsI can confirm that in order to preserve the scroll behaviour you need to remove the
e.preventDefault();
line, and if it's not needed for other things you can also not include thee
as a parameter to the function.