I've got a small jsfiddle - http://jsfiddle.net/qhguktsn/5/. When you tap the text at the top of the link (iOS mobile Safari), you get only the mouse events- no touch events at all, not even on the body. If you tap it on the bottom of the text, you get touch events. We depend on touch events for handling 300ms delay.
How can we get touch events for tapping on the top of the text as well as the bottom?
HTML:
<div style="margin-left:200px;margin-top:200px">
<a style="vertical-align:center;height: 20px, width: 20px;font-size:100px" href="javascript: void 0">text</a>
</div>
JS:
jQuery("a").on("mousedown", function() { document.body.appendChild(document.createTextNode("mousedown ")); });
jQuery("a").on("mouseup", function() { document.body.appendChild(document.createTextNode("mouseup ")); });
jQuery("a").on("touchstart", function() { document.body.appendChild(document.createTextNode("touchstart ")); });
jQuery("a").on("touchend", function() { document.body.appendChild(document.createTextNode("touchend ")); });
jQuery("a").on("click", function() { document.body.appendChild(document.createTextNode("click ")); });
jQuery(document.body).on("touchstart", function() { document.body.appendChild(document.createTextNode("body touchstart ")); });
jQuery(document.body).on("touchend", function() { document.body.appendChild(document.createTextNode("body touchend ")); });
This is know bug in Mobile Safari. https://bugs.webkit.org/show_bug.cgi?id=105406
There is another one as well with adding node form different document. https://bugs.webkit.org/show_bug.cgi?id=135628
In order to fix them there are several ways.
The first one is to use a small library called fastclick, which supports many mobile devices and OS.
The second options is to add event.stopPropagation(); event.preventDefault(); like that. You need both of them.
jQuery("a").on("mousedown", function(event) {
event.stopPropagation();
event.preventDefault();
document.body.appendChild(document.createTextNode("mousedown "));
});
The third option is by using the viewport meta tag like that <meta name="viewport" content="width=device-width, user-scalable=no">. This will eliminate all touch delays, without any workarounds. But, again on Safari it may not act like in the other browsers, because hey Safari is the new IE
There is also touch-action, but it's not supported in most of the mobile browsers. :(
The touch events on the body are due to the body element being shifted down by the margin-top, putting an outline on the body element outlines the touch-target:
body { outline: 1px solid red; }
http://jsfiddle.net/qhguktsn/11/
The second part of the mystery seems to be that the click target expands outside the touch-target:
Touching the red outline will not trigger a touch event on the body element, but the click event seems to fire when tapped anywhere within the grey -webkit-tap-highlight-color region which expands outside the anchor itself. Taps at the very top will therefore trigger click events on the anchor, but not touch events on the body.
I found that the touch event is not fired when clicking on an element contained by a position:fixed element that extends beyond the window. I handled this by making the parent container shorter (used JS to get the exact window height).
This problem was in an app UIWebview using iOS 10. (Yes, still using UIWebview)
All I want to do is capture the event that a user taps an and clear it. I can't get anything to work with iPhone 3GS.. There is barely any documentation on how to actually use jQuery mobile.. that I can find anyway.. so theese are my guesses mostly:
$("#wrap").live('pageinit', function() {
$('#search_field').live('tap',function(event) {
if ($(this).val() == "Search Applications") {
$(this).val('');
}
});
});
This borks my design and adds a "loading" header at the bottom of the page....
Edit: Seems like it randomly works on the 3GS but the most annoying is that just jQuery mobile destroys my site layout!! my submit button jumps down
()
just looking over you code are you trying to use a place holder and clear it if the user taps it? If so you can simply add an attribute to your HTML5 like this:
<imput type="text" placeholder="Search Applications" />
Live example:
http://jsfiddle.net/KVuvm/
http://jsfiddle.net/KVuvm/1/ (With jQM Look and Feel)
http://jsfiddle.net/KVuvm/14/ (if you still wanted to use JS)
I have a serious problem in Safari on iPad. The new contenteditable features doesn't seem to work with touchmove event!
code:
...
<script>
function doNothing(event) { return; }
function initIFrame() {
var iframe=document.getElementById("iframeedit");
iframe.contentWindow.document.designMode="on";
iframe.contentWindow.document.addEventListener("touchmove", doNothing, true);
}
</script>
</head>
<body onload="initIFrame()">
<iframe style="width:500ppx;height:200px" src="content.html" id="iframeedit"></iframe>
...
By adding touchmove somewhere to the document the editable content can not be edited anymore after a touchmove (hold finger down to get the magnifier). The cursor can be set but typing by onscreen keyboard is not allowed anymore.
Test script (for iPad + iOS5):
http://flyingdog.biz/tests/ipad/test2.html
Another test script which is working:
http://flyingdog.biz/tests/ipad/test1.html
As you can see in that other script I put a few lines of text in front of iFrame - very strange! I am looking for another/better workaround or did I have done something wrong? Without the touchmove event it is working but I need this for a good editing experience.
I found a workaround for this bug: It seems that the iframe document looses the focus after a touch event, especially when the copy&paste menu appears. To workaround this bug add a keydown event handler to the iframe-document and reset the focus to the document:
var iframeDoc = $(iframe.contentWindow.document);
iframeDoc.keydown(function(event) {
iframe.contentWindow.focus();
});
This fixes the bug mostly for me. Only if the user types very fast (e.g. on a connected bluetooth keyboard) it can happen that some keystrokes are lost, because the javascript keydown handler execution is a little bit delayed on the iPad.
I am building an iOS app in webkit, so my whole UI is a webview. on touchStart of any element near the outer boundary of the webview (that does not have a touchStart event bound to it), I get a translucent grey box overlay the full area of the webview. I've eliminated -webkit-tap-highlight-color or -webkit-touch-callout as causes. What do I do to remove this behavior?
just put this style, you still have the default actions but without the gray overlay
a {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
if you want to remove the actions panel, just put this
a {
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-touch-callout: none;
}
And there you go! clean links at last!
My working solution is to capture and preventDefault on the touchstart event of the body of the html document. All other more explicit event handlers are unaffected. I did run into an issue with select elements, which I addressed in the body event handler (I'm using jQuery):
$('body').live(
'touchstart',
function(e){
if(e.target.localName != 'select'){
e.preventDefault();
}
}
)
I have some css menus on my site that expand with :hover (without js)
This works in a semi-broken way on iDevices, for example a tap will activate the :hover rule and expand the menu, but then tapping elsewhere doesn't remove the :hover. Also if there is a link inside the element that is :hover'ed, you have to tap twice to activate the link (first tap triggers :hover, second tap triggers link).
I've been able to make things work nicely on iphone by binding the touchstart event.
The problem is that sometimes mobile safari still chooses to trigger the :hover rule from the css instead of my touchstart events!
I know this is the problem because when I disable all the :hover rules manually in the css, mobile safari works great (but regular browsers obviously don't anymore).
Is there a way to dynamically "cancel" :hover rules for certain elements when the user is on mobile safari?
See and compare iOS behavior here: http://jsfiddle.net/74s35/3/
Note: that only some css properties trigger the two-click behavior, e.g. display:none; but not background: red; or text-decoration: underline;
I found that ":hover" is unpredictable in iPhone/iPad Safari. Sometimes tap on element make that element ":hover", while sometimes it drifts to other elements.
For the time being, I just have a "no-touch" class at body.
<body class="yui3-skin-sam no-touch">
...
</body>
And have all CSS rules with ":hover" below ".no-touch":
.no-touch my:hover{
color: red;
}
Somewhere in the page, I have javascript to remove no-touch class from body.
if ('ontouchstart' in document) {
Y.one('body').removeClass('no-touch');
}
This doesn't look perfect, but it works anyway.
:hover isn't the issue here. Safari for iOS follows a very odd rule. It fires mouseover and mousemove first; if anything is changed during these events, 'click' and related events don't get fired:
mouseenter and mouseleave appear to be included, though they're not specified in the chart.
If you modify anything as a result of these events, click events won't get fired. That includes something higher up in the DOM tree. For example, this will prevent single clicks from working on your website with jQuery:
$(window).on('mousemove', function() {
$('body').attr('rel', Math.random());
});
Edit: For clarification, jQuery's hover event includes mouseenter and mouseleave. These will both prevent click if content is changed.
A better solution, without any JS, css class and viewport check: you can use Interaction Media Features (Media Queries Level 4)
Like this:
#media (hover) {
// properties
my:hover {
color: red;
}
}
iOS Safari supports it
More about:
https://www.jonathanfielding.com/an-introduction-to-interaction-media-features/
The browser feature detection library Modernizer includes a check for touch events.
It’s default behavior is to apply classes to your html element for each feature being detected. You can then use these classes to style your document.
If touch events are not enabled Modernizr can add a class of no-touch:
<html class="no-touch">
And then scope your hover styles with this class:
.no-touch a:hover { /* hover styles here */ }
You can download a custom Modernizr build to include as few or as many feature detections as you need.
Here's an example of some classes that may be applied:
<html class="js no-touch postmessage history multiplebgs
boxshadow opacity cssanimations csscolumns cssgradients
csstransforms csstransitions fontface localstorage sessionstorage
svg inlinesvg no-blobbuilder blob bloburls download formdata">
Some devices (as others have said) have both touch and mouse events. The Microsoft Surface for example has a touch screen, a trackpad AND a stylus which actually raises hover events when it is hovered above the screen.
Any solution that disables :hover based on the presence of 'touch' events will also affect Surface users (and many other similar devices). Many new laptops are touch and will respond to touch events - so disabling hovering is a really bad practice.
This is a bug in Safari, there's absolutely no justification for this terrible behavior. I refuse to sabotage non iOS browsers because of a bug in iOS Safari which has apparently been there for years. I really hope they fix this for iOS8 next week but in the meantime....
My solution:
Some have suggested using Modernizr already, well Modernizr allows you to create your own tests. What I'm basically doing here is 'abstracting' the idea of a browser that supports :hover into a Modernizr test that I can use throughout my code without hardcoding if (iOS) throughout.
Modernizr.addTest('workinghover', function ()
{
// Safari doesn't 'announce' to the world that it behaves badly with :hover
// so we have to check the userAgent
return navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? false : true;
});
Then the css becomes something like this
html.workinghover .rollover:hover
{
// rollover css
}
Only on iOS will this test fail and disable rollover.
The best part of such abstraction is that if I find it breaks on a certain android or if it's fixed in iOS9 then I can just modify the test.
Adding the FastClick library to your page will cause all taps on a mobile device to be turned into click events (regardless of where the user clicks), so it should also fix the hover issue on mobile devices. I edited your fiddle as an example: http://jsfiddle.net/FvACN/8/.
Just include the fastclick.min.js lib on your page, and activate via:
FastClick.attach(document.body);
As a side benefit, it will also remove the annoying 300ms onClick delay that mobile devices suffer from.
There are a couple of minor consequences to using FastClick that may or may not matter for your site:
If you tap somewhere on the page, scroll up, scroll back down, and then release your finger on the exact same position that you initially placed it, FastClick will interpret that as a "click", even though it's obviously not. At least that's how it works in the version of FastClick that I'm currently using (1.0.0). Someone may have fixed the issue since that version.
FastClick removes the ability for someone to "double click".
There are basically three scenarios:
User only has a mouse/pointer device and can activate :hover
User only has a touchscreen, and can not activate :hover elements
User has both a touchscreen and a pointer device
The originally accepted answer works great if only the first two scenarios are possible, where a user has either pointer or touchscreen. This was common when the OP asked the question 4 years ago. Several users have pointed out that Windows 8 and Surface devices are making the third scenario more likely.
The iOS solution to the problem of not being able to hover on touchscreen devices (as detailed by #Zenexer) is clever, but can cause straightforward code to misbehave (as noted by the OP). Disabling hover only for touchscreen devices means that you will still need to code a touchscreen friendly alternative. Detecting when a user has both pointer and touchscreen further muddies the waters (as explained by #Simon_Weaver).
At this point, the safest solution is to avoid using :hover as the only way a user can interact with your website. Hover effects are a good way of indicating that a link or button is actionable, but a user should not be required to hover an element to perform an action on your website.
Re-thinking “hover” functionality with touchscreens in mind has a good discussion about alternative UX approaches. The solutions provided by the answer there include:
Replacing hover menus with direct actions (always visible links)
Replacing on-hover menus with on-tap menus
Moving large amounts of on-hover content into a separate page
Moving forward, this will probably be the best solution for all new projects. The accepted answer is probably the second best solution, but be sure to account for devices that also have pointer devices. Be careful not to eliminate functionality when a device has a touchscreen just to work around iOS's :hover hack.
The JQuery version
in your .css use
.no-touch .my-element:hover
for all your hover rules
include JQuery and the following script
function removeHoverState(){
$("body").removeClass("no-touch");
}
Then in body tag add
class="no-touch" ontouchstart="removeHoverState()"
as soon as the ontouchstart fires the class for all hover states is removed
I agree disabling hover for touch is the way to go.
However, to save yourself the trouble of re-writing your css, just wrap any :hover items in #supports not (-webkit-overflow-scrolling: touch) {}
.hover, .hover-iOS {
display:inline-block;
font-family:arial;
background:red;
color:white;
padding:5px;
}
.hover:hover {
cursor:pointer;
background:green;
}
.hover-iOS {
background:grey;
}
#supports not (-webkit-overflow-scrolling: touch) {
.hover-iOS:hover {
cursor:pointer;
background:blue;
}
}
<input type="text" class="hover" placeholder="Hover over me" />
<input type="text" class="hover-iOS" placeholder="Hover over me (iOS)" />
Instead of only having hover effects when touch is not available I created a system for handling touch events and that has solved the problem for me. First, I defined an object for testing for "tap" (equivalent to "click") events.
touchTester =
{
touchStarted: false
,moveLimit: 5
,moveCount: null
,isSupported: 'ontouchend' in document
,isTap: function(event)
{
if (!this.isSupported) {
return true;
}
switch (event.originalEvent.type) {
case 'touchstart':
this.touchStarted = true;
this.moveCount = 0;
return false;
case 'touchmove':
this.moveCount++;
this.touchStarted = (this.moveCount <= this.moveLimit);
return false;
case 'touchend':
var isTap = this.touchStarted;
this.touchStarted = false;
return isTap;
default:
return true;
}
}
};
Then, in my event handler I do something like the following:
$('#nav').on('click touchstart touchmove touchend', 'ul > li > a'
,function handleClick(event) {
if (!touchTester.isTap(event)) {
return true;
}
// touch was click or touch equivalent
// nromal handling goes here.
});
Thanks #Morgan Cheng for the answer, however I've slightly modified the JS function for getting the "touchstart" (code taken from #Timothy Perez answer), though, you need jQuery 1.7+ for this
$(document).on({ 'touchstart' : function(){
//do whatever you want here
} });
Given the response provided by Zenexer, a pattern that requires no additional HTML tags is:
jQuery('a').on('mouseover', function(event) {
event.preventDefault();
// Show and hide your drop down nav or other elem
});
jQuery('a').on('click', function(event) {
if (jQuery(event.target).children('.dropdown').is(':visible') {
// Hide your dropdown nav here to unstick
}
});
This method fires off the mouseover first, the click second.
For those with common use case of disabling :hover events on iOS Safari, the simplest way is to use a min-width media query for your :hover events which stays above the screen width of the devices you are avoiding. Example:
#media only screen and (min-width: 1024px) {
.my-div:hover { // will only work on devices larger than iOS touch-enabled devices. Will still work on touch-enabled PCs etc.
background-color: red;
}
}
For someone still looking for a solution if none of the above worked,
Try this,
#media (hover: hover)
{
.Link:hover
{
color:#00d8fe;
}
}
This hover pseudo will only be applied for devices with pointers and works normal on touch devices with just .active classes.
Just look at the screen size....
#media (min-width: 550px) {
.menu ul li:hover > ul {
display: block;
}
}
heres the code you'll want to place it in
// a function to parse the user agent string; useful for
// detecting lots of browsers, not just the iPad.
function checkUserAgent(vs) {
var pattern = new RegExp(vs, 'i');
return !!pattern.test(navigator.userAgent);
}
if ( checkUserAgent('iPad') ) {
// iPad specific stuff here
}