How do I simulate a hover with a touch in touch enabled browsers?

227,577

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!
    }
});
Share:
227,577
Rich Bradshaw
Author by

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, 2022

Comments

  • Rich Bradshaw
    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
      Rich Bradshaw almost 14 years
      I'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
    Rich Bradshaw almost 14 years
    I didn't know about that onhover.call(p) bit, that's pretty cool. There's no off hover though...
  • Marcus
    Marcus almost 13 years
    Have 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
    Kzqai over 12 years
    Seems 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
    Kzqai over 12 years
    Whoops, that was a comment more applicable to the post above tthat actually uses jQuery, sorry.
  • Jacksonkr
    Jacksonkr almost 11 years
    This 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
    matpol over 10 years
    sorry to be pedantic but jquery is javacript
  • Codebeat
    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
    matpol over 10 years
    you 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
    Codebeat over 10 years
    A mix of native Javascript and jQuery, Satisfied?
  • Даниил Пронин
    Даниил Пронин almost 10 years
    I removed touchend and preventDefault() in my website and it works good but now menus doesn't automatically closed by tap out of target.
  • Kozy
    Kozy over 9 years
    this 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
    Darryl Young about 9 years
    I'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
    Roman Losev almost 9 years
    Worked for me. Thanks... The only thing - it lags a bit... I mean - after you press, it doesn't load instantly.
  • samuelkobe
    samuelkobe over 8 years
    Does this solution allow for the link to executed on the second tap?
  • myfunkyside
    myfunkyside over 8 years
    +1 for the user-select and touch-callout fixes, 'bout a year ago I was desperately looking for this functionality but couldn't find it anywhere
  • Richard Young
    Richard Young over 8 years
    Do 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
    Anselm about 8 years
    Use fastclick.js to remove the lag
  • Jack
    Jack about 8 years
    No it triggers the click on first touch still. I just tested it.
  • alsobubbly
    alsobubbly almost 8 years
    Is 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
    Petraeus about 7 years
    I 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
    Hunter Turner over 6 years
    Awesome solution! Quick and easy.
  • Flavien Volken
    Flavien Volken over 6 years
    Is the link content still relevant now?
  • Giorgio Tempesta
    Giorgio Tempesta over 5 years
    I 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 the e as a parameter to the function.