57

Working on a website that is also viewable on mobile and need to bind an action on both touchstart and mousedown.

Looks like this

 $("#roll").bind("mousedown touchstart", function(event){

 someAction();

It works fine on Iphone, but on Android it responds twice.

event.stopPropagation();
event.preventDefault();

Adding this code fixed it for Android Chrome, but NOT for Android default browser. Any other tricks that can fix the problem for all android?

skyisred
  • 6,855
  • 6
  • 37
  • 54
  • event.preventDefault() should stop the mousedown event. This question is old, so hopefully it is resolved now. I had the same problem and it turns out jquery touchpunch was causing it. – Maciej Krawczyk Mar 25 '16 at 09:21
  • 1
    This identical question might help someone more than answers here: https://stackoverflow.com/questions/7018919/how-to-bind-touchstart-and-click-events-but-not-respond-to-both/25133023# – Andrew Feb 04 '18 at 18:19

10 Answers10

37
element.on('touchstart mousedown', function(e) {
    e.preventDefault();
    someAction();
});

preventDefault cancels the event, as per specs

You get touchstart, but once you cancel it you no longer get mousedown. Contrary to what the accepted answer says, you don't need to call stopPropagation unless it's something you need. The event will propagate normally even when cancelled. The browser will ignore it, but your hooks will still work.

Mozilla agrees with me on this one:

calling preventDefault() on a touchstart or the first touchmove event of a series prevents the corresponding mouse events from firing

EDIT: I just read the question again and you say that you already did this and it didn't fix the Android default browser. Not sure how the accepted answer helped, as it does the same thing basically, just in a more complicated way and with an event propagation bug (touchstart doesn't propagate, but click does)

Radu C
  • 1,340
  • 13
  • 31
  • 1
    On android I get "Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted" – Curtis Jul 06 '16 at 19:06
  • 1
    you might need addEventListener('touchstart', FUNCTION, {passive: false}); – Walle Cyril Dec 15 '17 at 23:01
  • 2
    That works but then you can't even scroll the page by swiping. – Curtis Sep 22 '18 at 19:40
  • If you are trying to respond to both, touch and mouse events, there's a possibility that the thing you actually need are PointerEvents, which do handle both, touch and mouse events. – Michael Jun 23 '23 at 09:24
32

I have been using this function:

//touch click helper
(function ($) {
    $.fn.tclick = function (onclick) {

        this.bind("touchstart", function (e) { 
            onclick.call(this, e); 
            e.stopPropagation(); 
            e.preventDefault(); 
        });

        this.bind("click", function (e) { 
           onclick.call(this, e);  //substitute mousedown event for exact same result as touchstart         
        });   

        return this;
    };
})(jQuery);

UPDATE: Modified answer to support mouse and touch events together.

Andrew
  • 18,680
  • 13
  • 103
  • 118
Joshua
  • 3,465
  • 3
  • 28
  • 19
  • 9
    Be aware that Chrome and Firefox on Windows 8 will enable touch events when they detect a touch screen. So users can switch between mouse and touch input. With this code mouse events will be ignored. – gregers Feb 19 '13 at 09:27
  • 1
    @gregers you are right and wrong. Windows 8 do support both Touch and Mouse events, but when touching you can cancel "virtual" mouse events to only handle the touch, and it won't affect REAL mouse events because they don't fire Touch events. – daniel.gindi Jun 08 '13 at 21:41
  • @daniel.gindi My comment was for a previous revision of this answer: http://stackoverflow.com/posts/14202543/revisions – gregers Jun 12 '13 at 15:09
  • 13
    `touchstart` is equivalent to `mousedown` not `click`. Click requires the user to depress the button. I don't see how this has the exact same result. – George Reith Feb 17 '14 at 23:27
  • 1
    How would you unbind this event? Say if you want to add the listener temporarily and then stop the event from firing? – hofnarwillie Jul 03 '14 at 20:51
  • Note, as of Chrome 52, you now need to addEventListener with `{passive: false}` in order to do `preventDefault()`: https://www.chromestatus.com/feature/6540125429301248 – Marc Aug 15 '17 at 07:58
  • If you are trying to respond to both, touch and mouse events, there's a possibility that the thing you actually need are PointerEvents, which do handle both, touch and mouse events. – Michael Jun 23 '23 at 09:24
3

taking gregers comment on win8 and chrome/firefox into account, skyisred's comment doesn't look that dumb after all (:P @ all the haters) though I would rather go with a blacklist than with a whitelist which he suggested, only excluding Android from touch-binds:

var ua = navigator.userAgent.toLowerCase(),
isAndroid = ua.indexOf("android") != -1,
supportsPointer = !!window.navigator.msPointerEnabled,
ev_pointer = function(e) { ... }, // function to handle IE10's pointer events
ev_touch = function(e) { ... }, // function to handle touch events
ev_mouse = function(e) { ... }; // function to handle mouse events

if (supportsPointer) { // IE10 / Pointer Events
    // reset binds
    $("yourSelectorHere").on('MSPointerDown MSPointerMove MSPointerUp', ev_pointer);
} else {
    $("yourSelectorHere").on('touchstart touchmove touchend', ev_touch); // touch events
    if(!isAndroid) { 
        // in androids native browser mouse events are sometimes triggered directly w/o a preceding touchevent (most likely a bug)
        // bug confirmed in android 4.0.3 and 4.1.2
        $("yourSelectorHere").on('mousedown mousemove mouseup mouseleave', ev_mouse); // mouse events
    }
}

BTW: I found that mouse-events are NOT always triggered (if stopPropagation and preventDefault were used), specifically I only noticed an extra mousemove directly before a touchend event... really weird bug but the above code fixed it for me across all (tested OSX, Win, iOS 5+6, Android 2+4 each with native browser, Chrome, Firefox, IE, Safari and Opera, if available) platforms.

Jörn Berkefeld
  • 2,540
  • 20
  • 31
3

Wow, so many answers in this and the related question, but non of them worked for me (Chrome, mobil responsive, mousedown + touchstart). But this:

(e) => {
  if(typeof(window.ontouchstart) != 'undefined' && e.type == 'mousedown') return;

  // do anything...
}
Andreas Richter
  • 738
  • 6
  • 20
2

This is a very old question but I came across the same problem and found another solution that does not stopPropagation(), preventDefault() or sniff the type of device. I work on this solution with the assumption that the device supports both touch and mouse inputs.

Explanation: When a touch is initiated, the order of events is 1) touchstart 2) touchmove 3) touchend 4) mousemove 5) mousedown 6) mouseup 7) click. Based on this, we will mark a touch interaction from touchstart (first in chain) until click (last in chain). If a mousedown is registered outside of this touch interaction, it is safe to be picked up.

Below is the logic in Dart, should be very replicable in js.

var touchStarted = false;
document.onMouseDown.listen((evt) {
  if (!touchStarted) processInput(evt);
});
document.onClick.listen((evt) {
  touchStarted = false;
});
document.onTouchStart.listen((evt) {
  touchStarted = true;
  processInput(evt);
});

As you can see my listeners are placed on document. It is thus crucial that I do not stopPropagation() or preventDefault() these events so they can bubble up to other elements. This solution helped me single out one interaction to act on and hope it helps you too!

Nam Nguyen
  • 141
  • 1
  • 4
1

Fixed using this code

var mobile   = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); 
var start = mobile ? "touchstart" : "mousedown";
$("#roll").bind(start, function(event){
skyisred
  • 6,855
  • 6
  • 37
  • 54
  • 28
    -1: Use feature detection (like Joshua's answer) rather than UA detection. – Thomas Feb 06 '13 at 18:34
  • 3
    This is the best answer, and the only one that works. Joshua's answer doesn't use feature detection. The only feature you need to detect is android's current quirkiness. If you detect the feature of having touch events then it will disable mouse for touchscreen laptops. e.preventDefault() fails on android. – Curtis Jul 06 '16 at 19:10
  • 2
    what about devices with both touch and mouse ? – Walle Cyril Dec 15 '17 at 23:05
  • This doesn't work proper as well. I've tested with class scope and without the scope then each ways have its own issues. Using this code with class scope is just only firing `mousedown` no matter how are you trying to trigger the function using `touchstart` or `mousedown`. Without scope, it keeps firing the one result that you triggered at the first time. – l3lue Mar 24 '19 at 11:24
0

I recommend you try jquery-fast-click. I tried the other approach on this question and others. Each fixed one issue, and introduced another. fast-click worked the first time on Android, ios, desktop, and desktop touch browsers (groan).

Community
  • 1
  • 1
Adam Rabung
  • 5,232
  • 2
  • 27
  • 42
0

Write this code and add j query punch touch js.it will work simulate mouse events with touch events

function touchHandler(event)
{
    var touches = event.changedTouches,
        first = touches[0],
        type = "";
         switch(event.type)
    {
        case "touchstart": type = "mousedown"; break;
        case "touchmove":  type="mousemove"; break;        
        case "touchend":   type="mouseup"; break;
        default: return;
    }

    var simulatedEvent = document.createEvent("MouseEvent");
    simulatedEvent.initMouseEvent(type, true, true, window, 1, 
                              first.screenX, first.screenY, 
                              first.clientX, first.clientY, false, 
                              false, false, false, 0/*left*/, null);
    first.target.dispatchEvent(simulatedEvent);
    event.preventDefault();
}

function init() 
{
    document.addEventListener("touchstart", touchHandler, true);
    document.addEventListener("touchmove", touchHandler, true);
    document.addEventListener("touchend", touchHandler, true);
    document.addEventListener("touchcancel", touchHandler, true);    
} 
0

This native solution worked best for me:

  1. Add a touchstart event to the document settings a global touch = true.
  2. In the mousedown/touchstart handler, prevent all mousedown events when a touch screen is detected: if (touch && e.type === 'mousedown') return;
Raine Revere
  • 30,985
  • 5
  • 40
  • 52
-1

I think the best way is :

var hasTouchStartEvent = 'ontouchstart' in document.createElement( 'div' );

document.addEventListener( hasTouchStartEvent ? 'touchstart' : 'mousedown', function( e ) {
    console.log( e.touches ? 'TouchEvent' : 'MouseEvent' );
}, false );
LCB
  • 971
  • 9
  • 22
  • 2
    Then my mouse won't work on my touchscreen laptop. And if you add another event for mousedown, android will trigger both on touch. – Curtis Jul 06 '16 at 18:45
  • Most new laptops also support touching screen so will have these events.. Don't do this! – EoghanM Sep 07 '19 at 09:05