Why/when do I have to tap twice to trigger click on iOS
Asked Answered
C

14

42

Ok I feel like I'm crazy...

I'm looking at Mobile Safari on iOs 6.0. I can't seem to establish any rhyme or reason as to when tapping on an element will trigger click. In many cases, it seems I need to tap once to trigger a hover and then again to trigger a click.

The Mobile Safari spec says : "... The flow of events generated by one-finger and two-finger gestures are conditional depending on whether or not the selected element is clickable or scrollable... A clickable element is a link, form element, image map area, or any other element with mousemove, mousedown, mouseup, or onclick handlers... Because of these differences, you might need to change some of your elements to clickable elements..."

It goes on to suggest that the developer "...Add a dummy onclick handler, onclick = "void(0)", so that Safari on iOS recognizes the span element as a clickable element."

However, my testing has shown these statements to be false.

JsFiddle : http://jsfiddle.net/6Ymcy/1/

html

<div id="plain-div" onclick="void(0)">Plain Div</div>

js

document.getElementById('plain-div').addEventListener('click', function() {
   alert('click'); 
});

Try tapping the element on an iPad. Nothing Happens

But I digress. What is important to me is to find out the following question:

Exactly what are the criteria that determine when clicking on an element will fire a 'click' event on the first tap? As opposed to firing a 'hover' event on the first tap and a 'click' event on the second tap.

In my testing, anchor elements are the only elements that I can get to fire a click on the first tap, and then, only occasionally and inconsistently.

Here's where I start to feel crazy. I searched the internet far and wide and found next to nothing about this issue. Is it just me?! Does anybody know where there's been any discussion about the criteria for two-taps and or an approach to dealing with these limitations?

I'm happy to respond to questions/requests.

Thanks!

Cutright answered 17/7, 2013 at 21:49 Comment(1)
Yep, the onclick suggestion by Apple still does not actually work (iOS emulator) :-(Elfin
S
14

I had this same issue. The simplest solution is not to bind the mouseenter event on iOS (or any touch enabled target platform). If that is not bound the hover event won't get triggered and click is triggered on the first tap.

Sinful answered 9/4, 2015 at 20:16 Comment(0)
P
13

iOS will trigger the hover event if an element is "display: none;" in the normal state and "display: block;" or inline-block on :hover.

Putout answered 22/9, 2015 at 0:59 Comment(2)
I battled this for hours looking for a million other issues. In my case was using Angular ng-switch and ng-show to show/hide elements (by altering the display CSS value) which caused weird issues with iOS not triggering clicks.Elli
Wow. This is something really hard to find out. Thanks!Cabdriver
I
4

It is also worthwhile to mention that ':hover' pseudo-class may prevent 'click' event from firing.

As in mobile browsers click is sometimes used to replace hovering action (e.g. to show dropdown menu), they may trigger artificial 'hover' state on first click and then handle click on the second one.

See https://css-tricks.com/annoying-mobile-double-tap-link-issue/ for detailed explanation and examples of that.

Imagine answered 30/5, 2017 at 6:20 Comment(0)
E
3

I solved this issue by first detecting if it was an iphone, then binding the mouseup event to the function I was trying to call.

if ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))){ 
    $('foo').on('mouseup', function(){
        ...
    }
}

I tried other events but mouseup seemed to work best. Other events like touchend were firing even if the user was trying to scroll. Mouseup doesn't seem to get fired if you drag your finger after touching.

Credit David Walsh (and ESPN) for the iPhone detection. http://davidwalsh.name/detect-iphone

Esma answered 2/9, 2015 at 17:33 Comment(1)
This seems to have solved the problem for me - thanks.Ramsden
P
3

I was having this issue using Bootstrap, and I found out that the culprit was the tooltip. Remove the tooltip from the button and you don't need to tap it twice anymore.

Preparator answered 9/10, 2015 at 14:49 Comment(0)
M
2

my solution was to remove the :hover state from the css, and when you think about it, mobile browsers should not have :hover state, since there is no hover..

if you want to keep the hover state on desktop, you can use media query, like so:

.button {
    background: '#000'
}

@media (min-width: 992px) {
    .button:hover {
        background: '#fff'
    }
}
Mellisamellisent answered 27/3, 2018 at 10:37 Comment(0)
A
1

You need @media (hover) { /* Your styles */ }

As far as I can tell, this problem in various forms is still present.

In 2019, most, if not all of the above cases can be now ameliorated using a CSS only solution... it will however, require some stylesheet refactoring.

label {
  opacity:0.6  
}

label input[type=radio]:checked+span {
  opacity:1
}

.myClass::before { } /* Leave me empty to catch all browsers */

a:link { color: blue }
a:visited { color: purple }
a:hover { } /* Leave me empty to catch all browsers */
a:active { font-weight: bold }



/* Your styles */
@media (hover) {
  a:hover { color: red }

  .myClass::before { background: black }

  label:hover {
    opacity:0.8
  }
}

You can read in more detail here why Fastclick, :pseudo, <span>, just targeting "desktop" resolutions and first tap is hover and second tap is click are all fixed using @media (hover): https://css-tricks.com/annoying-mobile-double-tap-link-issue/

:hover doesn't offer the clarity it once did as stylus input, touch desktops and mobile have a disparate interpretation of the notion.

Addle answered 1/3, 2019 at 10:20 Comment(0)
S
0

The display:none; solution mentioned above works on iOS (not tested on later than 9.3.5), but not on Android.

A hacky css-only solution is to hide the link below the element using a minus z-index and to bring the link up to a positive z-index on :hover or first-touch (with a small transition delay). I guess one could achieve the same result with css translate instead of z-index. Works on iOS and Android.

In this way you can display a hover effect on a link on a touch-screen device with the first tap without activating the url until a second tap.

Swami answered 3/1, 2018 at 21:35 Comment(1)
if it does not work on Android, it's not a good solution.Hardened
K
0

you can use ontouchstart instead of onclick event on element and call the function focus() on this element if it is input :

document.getElementById('plain-div').addEventListener('touchstart', function() {
   //write body of your function here
    alert(“hi”);
  // if input needs double tap 
  this.focus();

});
Klink answered 1/3, 2020 at 21:14 Comment(0)
K
0

I found another very weird edge case that triggers this. If you remove display: none from any <button> with position: absolute during a touch event, then that touch event is also preventing any click events from firing.

Seems like the only fix is to delay the change to element.style.display by several milliseconds.

let counterValue = 0;
button.addEventListener("click", () => {
  counterValue++;
  counter.textContent = counterValue;
});

let cornerButtonVisible = false;
document.body.addEventListener("touchstart", () => {
  cornerButtonVisible = !cornerButtonVisible;
  cornerButton.style.display = cornerButtonVisible ? "" : "none";
});
#cornerButton {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 100;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover"
    />
  </head>
  <body>
    <button id="cornerButton" style="display:none">hidden</button>
    <br />
    <br />
    <br />
    <button id="button">count</button>
    <div id="counter">0</div>
  </body>
</html>
Kindhearted answered 27/3, 2024 at 20:28 Comment(0)
C
0

Works well on react:

import { useEffect } from "react";

export const useEnableSafariMobileClicks = () => {
  useEffect(() => {
    function handleTouchStart(this: Document, event: TouchEvent) {
      const target = event.target as HTMLElement;
      if (target.tagName === "A" || target.tagName === "BUTTON" || target.closest("a, button")) {
        try {
          event.preventDefault();
          target.click();
        } catch (er) {}
      }
    }

    document.addEventListener("touchstart", handleTouchStart, { passive: false });

    return () => {
      document.removeEventListener("touchstart", handleTouchStart);
    };
  }, []);
};
Christean answered 31/5, 2024 at 14:26 Comment(0)
C
-1

Never figured out the criteria, but this solved my problem by instantly triggering a click as soon as an element is tapped:

https://developers.google.com/mobile/articles/fast_buttons

I had to make a number of additions/modifications to their code to get it working correctly, let me know if you're interested in my method and I will try to post an explanation.

Cheers :)

Cutright answered 16/10, 2013 at 2:42 Comment(3)
I solved it using the FastClick JS Pplyfill — github.com/ftlabs/fastclickDecline
This link is now broken.Rebeccarebecka
@cale_b: web.archive.org/web/20140326101523/https://…Financier
O
-1

Ok I know this is probably not best practice but i just listened to the touch events and clicked the touched element.

html.addEventListener('touchstart', function(e) {
// get the element that was clicked
var element = e.target;
// create the click event if it happens inside where you want it to 
if(element.closest('.where')) {
    element.click();
}
return});
Overcash answered 28/2, 2023 at 15:4 Comment(0)
K
-2

I was googling around to see if i could help you out some and found this piece of code. Try modifying it to your likings and see if you can do what your trying. If you have troubles understanding it let me know and i'll elaborate more. Theres also more to it here where i found it

Jquery hover function and click through on tablet

$('clickable_element').live("touchstart",function(e){
    if ($(this).data('clicked_once')) {
        // element has been tapped (hovered), reset 'clicked_once' data flag and return true
        $(this).data('clicked_once', false);
        return true;
    } else {
        // element has not been tapped (hovered) yet, set 'clicked_once' data flag to true
        e.preventDefault();
        $(this).trigger("mouseenter"); //optional: trigger the hover state, as preventDefault(); breaks this.
        $(this).data('clicked_once', true);
    }
});
Kostival answered 17/7, 2013 at 23:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.