Safari provides an easy way to look at the URL under a link. On the Mac, choose View > Show Status Bar, hover your pointer over the link, and look at the bottom of the window. In iOS, touch and hold a link (don't press for 3D Touch) until a popover appears, showing the link and giving you options for opening it.
A "click" on an iPad will often simulate hover behaviors. Please note that the row-click with the side panel showing is not a hover effect but rather a click effect anyway. Cheers!
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.
Haven't tested this fully but since iOS fires touch events, this could work, assuming you are in a jQuery setting.
$('a').on('click touchend', function(e) {
var el = $(this);
var link = el.attr('href');
window.location = link;
});
The idea is that Mobile WebKit fires a touchend
event at the end of a tap so we listen for that and then redirect the browser as soon as a touchend
event has been fired on a link.
It is not entirely clear what your question is, but if you just want to eliminate the double click, while retaining the hover effect for the mouse, my advice is to:
touchstart
and mouseenter
.mouseleave
, touchmove
and click
.In order to simulate a mouse, browsers such as Webkit mobile fire the following events if a user touches and releases a finger on touch screen (like iPad) (source: Touch And Mouse on html5rocks.com):
touchstart
touchmove
touchend
mouseover
mouseenter
mouseover
, mouseenter
or mousemove
event changes the page content, the following events are never fired.mousemove
mousedown
mouseup
click
It does not seem possible to simply tell the webbrowser to skip the mouse events.
What's worse, if a mouseover event changes the page content, the click event is never fired, as explained on Safari Web Content Guide - Handling Events, in particular figure 6.4 in One-Finger Events. What exactly a "content change" is, will depend on browser and version. I've found that for iOS 7.0, a change in background color is not (or no longer?) a content change.
To recap:
touchstart
and mouseenter
.mouseleave
, touchmove
and click
.Note that there is no action on touchend
!
This clearly works for mouse events: mouseenter
and mouseleave
(slightly improved versions of mouseover
and mouseout
) are fired, and add and remove the hover.
If the user actually click
s a link, the hover effect is also removed. This ensure that it is removed if the user presses the back button in the web browser.
This also works for touch events: on touchstart
the hover effect is added. It is '''not''' removed on touchend
. It is added again on mouseenter
, and since this causes no content changes (it was already added), the click
event is also fired, and the link is followed without the need for the user to click again!
The 300ms delay that a browser has between a touchstart
event and click
is actually put in good use because the hover effect will be shown during this short time.
If the user decides to cancel the click, a move of the finger will do so just as normal. Normally, this is a problem since no mouseleave
event is fired, and the hover effect remains in place. Thankfully, this can easily be fixed by removing the hover effect on touchmove
.
That's it!
Note that it is possible to remove the 300ms delay, for example using the FastClick library, but this is out of scope for this question.
I've found the following problems with the following alternatives:
touchend
: This will incorrectly follow the link, even if the user only wanted to scroll or zoom, without the intention of actually clicking the link.touchend
that is used as a if-condition in subsequent mouse events to prevents state changes at that point in time. The variable is reset in the click event. This is a decent solution if you really don't want a hover effect on touch interfaces. Unfortunately, this does not work if a touchend
is fired for another reason and no click event is fired (e.g. the user scrolled or zoomed), and is subsequently trying to following the link with a mouse (i.e on a device with both mouse and touch interface).mouseover
or mousemove
event.See also iPad/iPhone double click problem and Disable hover effects on mobile browsers.
Seems there is a CSS solution after all. The reason Safari waits for a second touch is because of the background image (or elements) you usually assign on the :hover event. If there is none to be shown - you won't have any problems. The solution is to target iOS platform with secondary CSS file (or style in case of a JS approach) which overrides :hover background to inherit for example and keep hidden the elements you were going to display on mouse over:
Here is an example CSS and HTML - a product block with a starred label on mouse over:
HTML:
<a href="#" class="s"><span class="s-star"></span>Some text here</a>
CSS:
.s {
background: url(some-image.png) no-repeat 0 0;
}
.s:hover {
background: url(some-image-r.png) no-repeat 0 0;
}
.s-star {
background: url(star.png) no-repeat 0 0;
height: 56px;
position: absolute;
width: 72px;
display:none;
}
.s:hover .s-star {
display:block;
}
Solution (secondary CSS):
/* CSS */
/* Keep hovers the same or hidden */
.s:hover {
background:inherit;
}
.s:hover .s-star {
display:none;
}
No need to make overcomplicated.
$('a').on('touchend', function() {
$(this).click();
});
What worked for me is what others here have already said:
Don't show/hide elements on hover or mousemove (which is the event in my case).
Here's what Apple says (https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html):
A clickable element is a link, form element, image map area, or any other element with mousemove, mousedown, mouseup, or onclick handlers
If the user taps a clickable element, events arrive in this order: mouseover, mousemove, mousedown, mouseup, and click. Also, if the contents of the page changes on the mousemove event, no subsequent events in the sequence are sent. This behavior allows the user to tap in the new content.
So, you could use @woop's solution: detect the userAgent, check if it's and iOS device and then bind the event. I ended up using this technique because it suits my needs and it makes more sense do not bind hover events when you don't want it.
But... if you don't wanna mess with userAgents and still hide/show elements on hover/mousemove, i found out you can do so by using native javascript, like this:
$("body").on("mouseover", function() {
document.getElementsByTagName("my-class")[0].style.display = 'block'; //show element
document.querySelector(".my-selector div").style.display = 'none'; // hide element
});
This will work on the Desktop version and will do nothing on the mobile version.
And for a little more compatibility...
$("body").on("mouseover", function() {
if (document.getElementsByTagName && document.querySelector) { // check compatibility
document.getElementsByTagName("my-class")[0].style.display = 'block'; //show element
document.querySelector(".my-selector div").style.display = 'none'; // hide element
} else {
$(".my-class").show();
$(".my-selector div").hide();
}
});
cduruk's solution was quite effective, but caused problems on a few parts of my site. Because I was already using jQuery to add the CSS hover class, the easiest solution was to simply not add the CSS hover class on mobile devices (or more precisely, to ONLY add it when NOT on a mobile device).
Here was the general idea:
var device = navigator.userAgent.toLowerCase();
var ios = device.match(/(iphone|ipod|ipad)/);
if (!(ios)) {
$(".portfolio-style").hover(
function(){
$(this).stop().animate({opacity: 1}, 100);
$(this).addClass("portfolio-red-text");
},
function(){
$(this).stop().animate({opacity: 0.85}, 100);
$(this).removeClass("portfolio-red-text");
}
);
}
*code reduced for illustrative purposes
I had the following problems with the existing solutions, and found something that seems to solve all of them. This assumes you're aiming for something cross browser, cross device, and don't want device sniffing.
Using just touchstart
or touchend
:
Triggering mouseover
events on touchstart
and mouseout
on touchmove
has less serious consequences, but does interfere with the usual browser behaviour, for example:
touchstart
like a mouseover
, which is mouseout
ed on the next touchstart
. One way to see mouseover content in Android is therefore to touch the area of interest and wiggle your finger, scrolling the page slightly. Treating touchmove
as mouseout
breaks this.In theory, you could just add a flag with touchmove
, but iPhones trigger touchmove even if there's no movement. In theory, you could just compare the touchstart
and touchend
event pageX
and pageY
but on iPhones, there's no touchend
pageX
or pageY
.
So unfortunately to cover all bases it does end up a little more complicated.
$el.on('touchstart', function(e){
$el.data('tstartE', e);
if(event.originalEvent.targetTouches){
// store values, not reference, since touch obj will change
var touch = e.originalEvent.targetTouches[0];
$el.data('tstartT',{ clientX: touch.clientX, clientY: touch.clientY } );
}
});
$el.on('touchmove', function(e){
if(event.originalEvent.targetTouches){
$el.data('tstartM', event.originalEvent.targetTouches[0]);
}
});
$el.on('click touchend', function(e){
var oldE = $el.data('tstartE');
if( oldE && oldE.timeStamp + 1000 < e.timeStamp ) {
$el.data('tstartE',false);
return;
}
if( $el.data('iosTouchM') && $el.data('tstartT') ){
var start = $el.data('tstartT'), end = $el.data('tstartM');
if( start.clientX != end.clientX || start.clientY != end.clientY ){
$el.data('tstartT', false);
$el.data('tstartM', false);
$el.data('tstartE',false);
return;
}
}
$el.data('tstartE',false);
In theory, there are ways to get the exact time used for a longpress instead of just using 1000 as an approximation, but in practice it's not that simple and it's best to use a reasonable proxy.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With