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

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.

234396 次浏览

Without device (or rather browser) specific JS I'm pretty sure you're out of luck.

Edit: thought you wanted to avoid that until i reread your question. In case of Mobile Safari you can register to get all touch events similar to what you can do with native UIView-s. Can't find the documentation right now, will try to though.

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 ;)

One way to do it would be to do the hover effect when the touch starts, then remove the hover effect when the touch moves or ends.

This is what Apple has to say about touch handling in general, since you mention iPhone.

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!

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!

A mix of native Javascript and jQuery:

var gFireEvent = function (oElem,sEvent)
{
try {
if( typeof sEvent == 'string' && o.isDOM( oElem ))
{
var b = !!(document.createEvent),
evt = b?document.createEvent("HTMLEvents"):document.createEventObject();
if( b )
{  evt.initEvent(sEvent, true, true );
return !oElem.dispatchEvent(evt);
}
return oElem.fireEvent('on'+sEvent,evt);
}
} catch(e) {}
return false;
};




// Next you can do is (bIsMob etc you have to determine yourself):


if( <<< bIsMob || bIsTab || bisTouch >>> )
{
$(document).on('mousedown', function(e)
{
gFireEvent(e.target,'mouseover' );
}).on('mouseup', function(e)
{
gFireEvent(e.target,'mouseout' );
});
}

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.

Easyest solution I found: I had some < span > tags with :hover css rules in them. I switched for < a href="javascript:void(0)" > and voilà. The hover styles in iOS started working.

Use can use CSS too, add focus and active (for IE7 and under) to the hidden link. Example of a ul menu inside a div with class menu:

.menu ul ul {display:none; position:absolute; left:100%; top:0; padding:0;}
.menu ul ul ul {display:none; position:absolute; top:0; left:100%;}
.menu ul ul a, .menu ul ul a:focus, .menu ul ul a:active { width:170px; padding:4px 4%; background:#e77776; color:#fff; margin-left:-15px; }
.menu ul li:hover > ul { display:block; }
.menu ul li ul li {margin:0;}

It's late and untested, should work ;-)

The mouse hover effect cannot be implemented in touch device . When I'm appeared with same situation in safari ios I used :active in css to make effect.

ie.

p:active {
color:red;
}

In my case its working .May be this is also the case that can be used with out using javascript. Just give a try.

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!
}
});

Add this code and then set class "tapHover" to elements which you would like to work this way. First time you tap an element it will gain pseudoclass ":hover" and class "tapped". Click event will be prevented. Second time you tap the same element - click event will be fired.

// Activate only in devices with touch screen
if('ontouchstart' in window)
{
// this will make touch event add hover pseudoclass
document.addEventListener('touchstart', function(e) {}, true);


// modify click event
document.addEventListener('click', function(e) {
// get .tapHover element under cursor
var el = jQuery(e.target).hasClass('tapHover')
? jQuery(e.target)
: jQuery(e.target).closest('.tapHover');


if(!el.length)
return;


// remove tapped class from old ones
jQuery('.tapHover.tapped').each(function() {
if(this != el.get(0))
jQuery(this).removeClass('tapped');
});


if(!el.hasClass('tapped'))
{
// this is the first tap
el.addClass('tapped');
e.preventDefault();
return false;
}
else
{
// second tap
return true;
}
}, true);
}
.box {
float:		left;
	

display:	inline-block;
margin:		50px 0 0 50px;
width:		100px;
height:		100px;
overflow:	hidden;
	

font-size:	20px;
	

border:		solid 1px black;
}
.box.tapHover {
background:	yellow;
}
.box.tapped {
border:		solid 3px red;
}
.box:hover {
background:	red;
}
<div class="box" onclick="this.innerHTML = Math.random().toFixed(5)"></div>
<div class="box tapHover" onclick="this.innerHTML = Math.random().toFixed(5)"></div>
<div class="box tapHover" onclick="this.innerHTML = Math.random().toFixed(5)"></div>

My personal taste is to attribute the :hover styles to the :focus state as well, like:

p {
color: red;
}


p:hover, p:focus {
color: blue;
}

Then with the following HTML:

<p id="some-p-tag" tabindex="-1">WOOOO</p>

And the following JavaScript:

$("#some-p-tag").on("touchstart", function(e){
e.preventDefault();
var $elem = $(this);


if($elem.is(":focus")) {
//this can be registered as a "click" on a mobile device, as it's a double tap
$elem.blur()
}
else {
$elem.focus();
}
});

Solved 2019 - Hover on Touch

It now seems best to avoid using hover altogether with ios or touch in general. The below code applies your css as long as touch is maintained, and without other ios flyouts. Do this;

  1. Jquery add: $("p").on("touchstart", function(e) { $(this).focus(); e.preventDefault(); });

  2. CSS: replace p:hover with p:focus, and add p:active

Options;

  • replace jquery p selector with any class etc

  • to have the effect remain, keep p:hover as well, and add body{cursor:ponter;} so a tap anywhere ends it

  • try click & mouseover events as well as touchstart in same code (but not tested)

  • remove e.preventDefault(); to enable users to utilise ios flyouts eg copy

Notes

  • only tested for text elements, ios may treat inputs etc differently

  • only tested on iphone XR ios 12.1.12, and ipad 3 ios 9.3.5, using Safari or Chrome.

You can use @media (hover: none)

.block:hover {
background: red;
}


@media (hover: none) {
.block:hover {
background: white;
}
}
<div class="block">
hello
</div>