How to bind 'touchstart' and 'click' events but not respond to both?
Asked Answered
E

36

217

I'm working on a mobile web site that has to work on a variety of devices. The ones giving me a headache at the moment are BlackBerry.

We need to support both keyboard clicks as well as touch events.

Ideally I'd just use:

$thing.click(function(){...})

but the issue we're running into is that some of these blackberry devices have a very annoying delay from the time of the touch to it triggering a click.

The remedy is to instead use touchstart:

$thing.bind('touchstart', function(event){...})

But how do I go about binding both events, but only firing one? I still need the click event for keyboard devices, but of course, don't want the click event firing if I'm using a touch device.

A bonus question: Is there anyway to do this and additionally accommodate browsers that don't even have a touchstart event? In researching this, it looks like BlackBerry OS5 doesn't support touchstart so will also need to rely on click events for that browser.

ADDENDUM:

Perhaps a more comprehensive question is:

With jQuery, is it possible/recommended to handle both touch interactions and mouse interactions with the same bindings?

Ideally, the answer is yes. If not, I do have some options:

  1. We use WURFL to get device info so could create our own matrix of devices. Depending on the device, we'll use touchstart OR click.

  2. Detect for touch support in the browser via JS (I need to do some more research on that, but it seems like that is doable).

However, that still leaves one issue: what about devices that support BOTH. Some of the phones we support (namely the Nokias and BlackBerries) have both touch screens and keyboards. So that kind of takes me full circle back to the original question...is there a way to allow for both at once somehow?

Eisenhower answered 10/8, 2011 at 22:56 Comment(9)
You're better off binding to touchstart and touchend and writing your own click logic along side your touch logic. The built-in click callback as no knowledge of touches.Plano
I'm not sure I follow, Justin. Wouldn't I still have both a touchstart and click event bound to it?Eisenhower
@DA - no, you wouldn't bind to the .click() callback at all. I'll try to write an answer in some sudo code. I dont have a touch device handy to write up real code :)Plano
Ah, but to clarify, I still need click events, as there will be people accessing this site with non-touch devices.Eisenhower
@DA - you can use detect and use either set depending on the situation. If you only deal with clicks/taps and nothing else at all, use .click() if you want to do more with touched, use both and pick the proper logic to use by detecting if the browser supports touch events :)Plano
ah...that didn't occur to me to test for touch events. That does leave one remaining scenario, though...touch devices that also have keyboards. ;o)Eisenhower
Using .bind('touchstart mouseup') will solve it (based on one of the comments below)Suture
Check out the .one method api.jquery.com/oneSuture
@Suture sick answer, A++Epimenides
F
145

Update: Check out the jQuery Pointer Events Polyfill project which allows you to bind to "pointer" events instead of choosing between mouse & touch.


Bind to both, but make a flag so the function only fires once per 100ms or so.

var flag = false;
$thing.bind('touchstart click', function(){
  if (!flag) {
    flag = true;
    setTimeout(function(){ flag = false; }, 100);
    // do something
  }

  return false
});
Frightened answered 11/8, 2011 at 0:14 Comment(19)
hmm...it feels hacky, but that could work. The catch is that on some of these devices, it's a noticeable lag...perhaps almost a second...which would be annoying for others on a faster device.Eisenhower
Instead of using click change it to mousedown... maybe the long delay is the difference between mousedown and mouseup which is how a click is determined.Frightened
It's not perfect, but actually just swapping click for mousedown shows an improvement. Not ideal, but that could be an interim solution.Eisenhower
This is the solution I went with. Thanks! What I do is I flag an item on touchend by applying a class of touched. This fires before the click event is called. I then add a click event that first checks for the existence of that class. If it's there, we assume the touch events fired and we don't do anything with click other than remove the class name to 'reset' it for the next interaction. If the class isn't there, then we assume they had used a keyboard to click the item, and trigger the function from there.Eisenhower
There is a problem with relying on touchend. It doesn't fire if you touch then drag your finger off of the element. What I've ended up doing is binding to touchend, touchmove and touchcancel to indicate that the touch event has ended - so, in your case, add the "touched" class when any of those events have fire off.Frightened
NB: You cannot handle ghost click events by element, they are triggered by screen location. If the touch events change the page in some way, the delayed click will fire against whatever element is in that location after the page change.Parotitis
You should be aware that most phone browsers has a 300ms delay on the click event, so you should increase the deleay to at least 300, See this article on the 300ms delay issue. This is basically to enable "double click to zoom" functionality that was a very important feature in the early iPhone days, when most web sites were designed to view on a large desktop screen.Exorbitance
This question has been viewed close to 100k times at this point. This seems like an extraordinarily common use case/problem. However, this answer and others on this and similar questions all seem hacky. The google solution, while more thoroughly thought through than most, seems like merely a more robust hack. For something as simple as a click handler, it seems like the solution should be simple. Has no one found a non-hack solution for this issue?Secessionist
Huh. I poked around a bit and it seems maybe the best we have at this point is something like Tappy! or FastClick. In the end vendors have to agree and implement, and then 95%+ of the mobile user base has to receive an update that includes the solution. The pessimist in me says 5-10 years. :(Secessionist
Would be nice to make this a general event like 'touchclick' or something usable throughout a site without adding it manually each time… or else let the computer automatically search for click events and then change them…Maurilla
As I said above this is a wonderful reliable solution. Only that it breaks in some other places part of my code due to the return false at the end. Also I read in another place that using jQuery it is recommended to use e.preventDefault(). So I wonder if this snippet still prevents firing the event twice changing it to something like so:var flag = false; $thing.bind('touchstart click', function(e){e.preventDefault(); if (!flag) { flag = true; setTimeout(function(){ flag = false; }, 100); // do something } });Maurilla
It doesn't work on blackberry Z30 in default browser. But Rafael Fragoso - his solution works perfectly.Impasto
Not saying that this is the best solution for all cases, but it's definitely a pretty decent approach. That being said, why use setTimeout, given that this relies on jQuery anyway? Check out $.throttle and/or $.debounce (benalman.com/code/projects/jquery-throttle-debounce/docs/files/…)Lanneret
Thx for adding jQuery PEP linkMeemeece
I've used this on many projects and it works really well. The project that I'm working on at the moment is using $.throttle for something else so I've ended up using the following. $thing.bind('touchstart click', $.throttle( 600, true, myFunc ) );Resolution
While this may have been the best solution at the time it was written, I highly recommend checking out Roy's solution below, which is both easier and less hacky: https://mcmap.net/q/125442/-how-to-bind-39-touchstart-39-and-39-click-39-events-but-not-respond-to-bothSyndicate
@Syndicate comment needs upvoting, it is the best answer in 2019.Jonasjonathan
You could also check for touch capabilities before adding event listeners. #4817529Meek
@NavidKhan the issue with that was (and probably still is to some extent?) that there are devices with both touch capabilities and keyboard-based 'click' functionality.Eisenhower
G
76

This is the fix that I "create" and it take out the GhostClick and implements the FastClick. Try on your own and let us know if it worked for you.

$(document).on('touchstart click', '.myBtn', function(event){
        if(event.handled === false) return
        event.stopPropagation();
        event.preventDefault();
        event.handled = true;

        // Do your magic here

});
Glandular answered 16/7, 2012 at 15:33 Comment(16)
the only method that worked perfectly for me. Don't forget to add event inside the function brackets though :)!Allain
Thanks Jonathan, that is now fixed.Heartrending
I know I'm asking a lot, but is there a way to wrap this up as a tiny function so that it can be used on multiple items?Voluble
helgatheviking: jsbin.com/ijizat/25 In the JavaScript there's a function called TouchClick that incorporates the above.Runofthemill
This solved my issue apart from when using the latest jquery I needed to use: on('click touchstart'Mineralize
Same for me - .live() doesn't seem to work but .on() works fine.Bog
live() has been deprecated for a while, so using on() is actually preferable anyway.Canica
I HIGHLY prefer this method of all of the answers for this question because it's a) not testing for device capabilities and b) not using setTimeout. In my experience, solutions to problems like these that use setTimeout may work for most cases but the timing of events is fairly arbitrary and device specific.Guillaume
I like this approach too. For the purpose of updating the linked JSbin TouchClick function:Secularism
This does not work on all Android 4.3 phones (i.e. on the Samsung Galaxy S3 it is still triggered twice).Battology
This won't work because by passing 'event' in the anonymous function it becomes a local object within that function, so event.handled will equal undefined every time the event is triggered. Here's a solution: set the 'handled' flag in the target element itself by using jQuery.data().Loxodromics
Since I needed this in multiple places on the project I am doing, I turned this little snippet into a quick fix jquery plugin. To see it working, head over to the following jsfiddle.net/bw62pdzz Thanks Rafael FragosoTheurich
You have event.stopPropagation(); and event.preventDefault(); from my understanding (and testing) the event can only be fired once with this. so why even check if(event.handled !== true)? For me it is not necessary or do I miss something?Stob
I think you are correct, nbar. The variable serves no purpose because it will never be set to false. This answer is the same as Jonathan's below and practically identical to using return false as suggested by Roy. Problem with preventing default behaviour on touchstart is that it disables native implementations. One will not be able to scroll the page when starting at that element for example.Iconoclast
Here's a demo that confirms what 10basetom mentioned: codepen.io/KWZeegIconoclast
Shouldn’t event.handled be checked for the value true? As far as I can tell it’s never false. It starts out as undefined, and then you want to tell whether it’s been set to true.Tanta
B
49

You could try something like this:

var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);
Bidwell answered 23/9, 2011 at 14:21 Comment(6)
I think the issue with that is it's testing device capabilities rather than what the user is doing, right? The challenge is we need to support touch devices that also have keyboards (so they could be using both)Eisenhower
This is not a good idea - will break the event for a user with a touch screen and a mouse when using the mouse (laptops with touch screens are becoming quite common).Sikora
Excellent article related to this: hacks.mozilla.org/2013/04/…Crandell
Windows 8 will always wire up the 'touchstart' event if this is used because 'ontouchstart' will return 'true' for this OS. So, probably not the best answer unless your code will only be running on true touchscreen devices, but then if that's the case, you don't need the check at all.Orin
On laptops with a touch screen the user won't be able to click anything using this code in chrome (at least chrome 46). IE 11 seems to work though.Bookstore
I ended up using a version of this approach where, instead of looking for document.ontouchstart, I set the clickEventType the first time the user touches the screen. It doesn't solve for users who continually go back and forth between a mouse and a touchscreen on the same device, but at least it won't switch to "touch mode" without the user ever even touching the screen just because the event is possible.Middaugh
A
43

Usually this works as well:

$('#buttonId').on('touchstart click', function(e){
    e.stopPropagation(); e.preventDefault();
    //your code here

});
Allain answered 3/11, 2012 at 22:28 Comment(3)
@Allain wrong, if you're using newer jQuery versions. Live was deprecated in version 1.7.Kress
I also tried (and I read it elsewhere - not my idea) with e.stopPropagation(); e.preventDefault(); and it works (for me) almost but I discovered later on that in fact it works as predicted 20 times but 1 time not, so it is not bulletproof. Look here: #25671553 Appreciate your comments on that issue!Maurilla
@MikeBarwick Could you please explain your comment or give a link that explains it? Thank you in advance.Incogitable
A
28

Just adding return false; at the end of the on("click touchstart") event function can solve this problem.

$(this).on("click touchstart", function() {
  // Do things
  return false;
});

From the jQuery documentation on .on()

Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }.

Actinon answered 6/4, 2016 at 13:20 Comment(3)
works great, fires only once on devices with both - this seems like the cleanest least hacky solution to meJulianejuliann
Worked best for me and actually made sense why.Lamkin
Any downsides to this? Why is such a simple solution not widely known?Squarerigger
G
11

I had to do something similar. Here is a simplified version of what worked for me. If a touch event is detected, remove the click binding.

$thing.on('touchstart click', function(event){
  if (event.type == "touchstart")
    $(this).off('click');

  //your code here
});

In my case the click event was bound to an <a> element so I had to remove the click binding and rebind a click event which prevented the default action for the <a> element.

$thing.on('touchstart click', function(event){
  if (event.type == "touchstart")
    $(this).off('click').on('click', function(e){ e.preventDefault(); });

  //your code here
});
Googins answered 18/12, 2013 at 12:7 Comment(1)
I wish this worked as is, but it does not. $(this) does not point to what you think it does.Versatile
P
8

I succeeded by the following way.

Easy Peasy...

$(this).on('touchstart click', function(e){
  e.preventDefault();
  //do your stuff here
});
Peres answered 18/4, 2012 at 14:1 Comment(5)
won't that fire twice on devices that register both a click and a touchstart?Eisenhower
Sorry, I got you know. Yes you are right a touch will generate touchstart event and click event as well. This is called ghost click. Google has implemented a solution for that. Might not straight forward but works perfectly. Here is the link. code.google.com/mobile/articles/fast_buttons.html#ghostPeres
might be trivial but you're missing the e parameter in function()Sezen
@Peres this was a great fix however I found better results using .onSardinia
Thanks @Neil. Yes using .on is a better idea as it might be removed in future version. I've changed my example from bind to on.Peres
N
6

I believe the best practice is now to use:

$('#object').on('touchend mouseup', function () { });

touchend

The touchend event is fired when a touch point is removed from the touch surface.

The touchend event will not trigger any mouse events.


mouseup

The mouseup event is sent to an element when the mouse pointer is over the element, and the mouse button is released. Any HTML element can receive this event.

The mouseup event will not trigger any touch events.

EXAMPLE

$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>

EDIT (2017)

As of 2017, browsers starting with Chrome are making steps towards making the click event .on("click") more compatible for both mouse and touch by eliminating the delay generated by tap events on click requests.

This leads to the conclusion that reverting back to using just the click event would be the simplest solution moving forward.

I have not yet done any cross browser testing to see if this is practical.

Nuristan answered 21/4, 2017 at 8:9 Comment(2)
This seemed promising to me, so I messed around with it a bunch, but ultimately I found mouseup to have unpredictable results, especially when using a trackpad instead of a traditional mouse.Middaugh
Using the mouseup event can lead to issues when using scrolling content that you want a user to drag around but not click. I think the best is to use multiple listeners with proper returns being set to false to restrict further bubbling.Dynamotor
P
3

Generally you don't want to mix the default touch and non-touch (click) api. Once you move into the world of touch it easier to deal only with the touch related functions. Below is some pseudo code that would do what you want it to.

If you connect in the touchmove event and track the locations you can add more items in the doTouchLogic function to detect gestures and whatnot.

var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;

$thing.bind('touchstart'), function() {
     var d = new Date();
     touchStartTime = d.getTime();
     touchStartLocation = mouse.location(x,y);
});

$thing.bind('touchend'), function() {
     var d = new Date();
     touchEndTime= d.getTime();
     touchEndLocation= mouse.location(x,y);
     doTouchLogic();
});

function doTouchLogic() {
     var distance = touchEndLocation - touchStartLocation;
     var duration = touchEndTime - touchStartTime;

     if (duration <= 100ms && distance <= 10px) {
          // Person tapped their finger (do click/tap stuff here)
     }
     if (duration > 100ms && distance <= 10px) {
          // Person pressed their finger (not a quick tap)
     }
     if (duration <= 100ms && distance > 10px) {
          // Person flicked their finger
     }
     if (duration > 100ms && distance > 10px) {
          // Person dragged their finger
     }
}
Plano answered 11/8, 2011 at 0:46 Comment(4)
I suppose that's the crux of the question: does it even make sense to try and support both models with one code base. I'll update my question with some more scenarios.Eisenhower
"Generally you don't want to mix the default touch and non-touch" after going through this, I agree. The problem is that they keep making touch devices with keyboards, which is a headache for us developers.Eisenhower
I agree that it would be much much easier to do at the beginning the final js decision touch or no touch. What easy world it would be! But what is this about those damned devices click and touch sensible? Is it worth to have all those headaches because of them? Is this the future I ask myself?Maurilla
I don't like the idea of having multiple definitions of functions. My company is currently working on a project where an iPad is not specialy handled as mobile device and you still need the touchend event. But touchend doesn't work on normal desktop versions. So the solution of @mottie is absolute fine for that scenario.Hussey
N
3

check fast buttons and chost clicks from google https://developers.google.com/mobile/articles/fast_buttons

Neopythagoreanism answered 20/7, 2012 at 9:50 Comment(0)
U
3

Well... All of these are super complicated.

If you have modernizr, it's a no-brainer.

ev = Modernizr.touch ? 'touchstart' : 'click';

$('#menu').on(ev, '[href="#open-menu"]', function(){
  //winning
});
Usherette answered 16/5, 2014 at 4:26 Comment(2)
Can you explain what that does? I believe it checks to see if the device supports touch and if not, uses click. The catch is (or at least was, when I wrote the question) was that some devices support both. And on those devices, I still need to handle both events, as the trigger could come from either touch or click.Eisenhower
I recommend a bit more explanation.Spunk
S
2

Another implementation for better maintenance. However, this technique will also do event.stopPropagation (). The click is not caught on any other element that clicked for 100ms.

var clickObject = {
    flag: false,
    isAlreadyClicked: function () {
        var wasClicked = clickObject.flag;
        clickObject.flag = true;
        setTimeout(function () { clickObject.flag = false; }, 100);
        return wasClicked;
    }
};

$("#myButton").bind("click touchstart", function (event) {
   if (!clickObject.isAlreadyClicked()) {
      ...
   }
}
Seascape answered 28/4, 2012 at 14:33 Comment(0)
B
2

Just for documentation purposes, here's what I've done for the fastest/most responsive click on desktop/tap on mobile solution that I could think of:

I replaced jQuery's on function with a modified one that, whenever the browser supports touch events, replaced all my click events with touchstart.

$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
    on: (function(){
        var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
        return function( types, selector, data, fn, one ) {
            if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
            return this._on( types, selector, data, fn);
        };
    }()),
});

Usage than would be the exact same as before, like:

$('#my-button').on('click', function(){ /* ... */ });

But it would use touchstart when available, click when not. No delays of any kind needed :D

Bleach answered 25/4, 2013 at 17:28 Comment(2)
The catch with this would be devices that support BOTH touch AND keyboards where you need to ensure you are accommodating both interactions.Eisenhower
Good observation @Eisenhower I added a check to only replace the click event if you're not using any touch event in combination with it. Stil won't be a solution for every project, but I'm sure would be a good fit for many.Bleach
P
2

I just came up with the idea to memorize if ontouchstart was ever triggered. In this case we are on a device which supports it and want to ignore the onclick event. Since ontouchstart should always be triggered before onclick, I'm using this:

<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>
Pimiento answered 15/12, 2014 at 15:48 Comment(1)
Since users might use touch and mouse during the same page visit, the problem with this snippet is that it registers the touchAvailable flag once instead of per event. The jQuery plugin from https://mcmap.net/q/125442/-how-to-bind-39-touchstart-39-and-39-click-39-events-but-not-respond-to-both does the same as your code, but can tell for a mouse event whether it was triggered by touch or mouse on an event basis. Not only ie. on click, but also on mouseleave which might be triggered seconds (or minutes) after mouseenter (ideal for flyout menu's that should expand on mouseover with mouse gestures or click when using touch).Caviar
A
2

You could try like this:

var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);
Alfonsoalfonzo answered 20/8, 2015 at 2:34 Comment(1)
what about devices that support touch and mouseMcglynn
M
2

In my case this worked perfectly:

jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
    jQuery(this).off('mouseup');
}
});

The main problem was when instead mouseup I tried with click, on touch devices triggered click and touchend at the same time, if i use the click off, some functionality didn't worked at all on mobile devices. The problem with click is that is a global event that fire the rest of the event including touchend.

Mohammedanism answered 2/10, 2017 at 14:21 Comment(0)
T
1

Being for me the best answer the one given by Mottie, I'm just trying to do his code more reusable, so this is my contribution:

bindBtn ("#loginbutton",loginAction);

function bindBtn(element,action){

var flag = false;
$(element).bind('touchstart click', function(e) {
    e.preventDefault();
    if (!flag) {
        flag = true;
        setTimeout(function() {
            flag = false;
        }, 100);
        // do something
        action();
    }
    return false;
});
Twodimensional answered 5/8, 2014 at 7:8 Comment(0)
A
1

This worked for me, mobile listens to both, so prevent the one, which is the touch event. desktop only listen to mouse.

 $btnUp.bind('touchstart mousedown',function(e){
     e.preventDefault();

     if (e.type === 'touchstart') {
         return;
     }

     var val = _step( _options.arrowStep );
               _evt('Button', [val, true]);
  });
Animated answered 4/12, 2015 at 9:57 Comment(0)
A
1

This hasn't been mentioned here, but you may want to check out this link: https://joshtronic.com/2015/04/19/handling-click-and-touch-events-on-the-same-element/

To recap for posterity, instead of trying to assign to both handlers and then sort out the result, you can simply check if the device is a touchscreen or not and only assign to the relevant event. Observe:

var clickEvent = (function() {
  if ('ontouchstart' in document.documentElement === true)
    return 'touchstart';
  else
    return 'click';
})();

// and assign thusly:

el.addEventListener( clickEvent, function( e ){ 
    // things and stuff
});

I am using this to bind my events so that I can test on touchscreens that handle both touchstart and click events which would fire twice, and on my development PC which only hears the click

One problem the author of that link mentions though, is touchscreen laptops designed to handle both events:

I learned about a third device I was not considering, the touchscreen laptop. It’s a hybrid device that supports both touch and click events. Binding one event means only that event be supported. Does that mean someone with a touchscreen and mouse would have to explicitly touch because that’s the only event I am handling?

Binding touchstart and click seemed ideal to handle these hybrid devices. To keep the event from firing twice, I added e.stopPropagation() and e.preventDefault() to the callback functions. e.stopPropagation() stops events from “bubbling up” to their parents but also keeps a second event from firing. I included e.preventDefault() as a “just in case” but seems like it could be omitted.

Acetate answered 23/9, 2018 at 23:38 Comment(0)
C
0

I am also working on an Android/iPad web app, and it seems that if only using "touchmove" is enough to "move components" ( no need touchstart ). By disabling touchstart, you can use .click(); from jQuery. It's actually working because it hasn't be overloaded by touchstart.

Finally, you can binb .live("touchstart", function(e) { e.stopPropagation(); }); to ask the touchstart event to stop propagating, living room to click() to get triggered.

It worked for me.

Complainant answered 27/3, 2012 at 4:12 Comment(2)
How would you disable touchstart?Botfly
I believe that you "could" do something like: jQuery("#my_element").on('touchstart', function(e){e.preventDefault()});Complainant
P
0

There are many things to consider when trying to solve this issue. Most solutions either break scrolling or don't handle ghost click events properly.

For a full solution see https://developers.google.com/mobile/articles/fast_buttons

NB: You cannot handle ghost click events on a per-element basis. A delayed click is fired by screen location, so if your touch events modify the page in some way, the click event will be sent to the new version of the page.

Parotitis answered 18/7, 2012 at 14:29 Comment(0)
S
0

It may be effective to assign to the events 'touchstart mousedown' or 'touchend mouseup' to avoid undesired side-effects of using click.

Steelhead answered 16/11, 2012 at 23:50 Comment(0)
G
0

Taking advantage of the fact that a click will always follow a touch event, here is what I did to get rid of the "ghost click" without having to use timeouts or global flags.

$('#buttonId').on('touchstart click', function(event){
    if ($(this).data("already")) {
        $(this).data("already", false);
        return false;
    } else if (event.type == "touchstart") {
        $(this).data("already", true);
    }
    //your code here
});

Basically whenever an ontouchstart event fires on the element, a flag a set and then subsequently removed (and ignored), when the click comes.

Galegalea answered 8/2, 2013 at 21:11 Comment(0)
K
0

If you are using jQuery the following worked pretty well for me:

var callback; // Initialize this to the function which needs to be called

$(target).on("click touchstart", selector, (function (func){
    var timer = 0;
    return function(e){
        if ($.now() - timer < 500) return false;
        timer = $.now();
        func(e);
    }
})(callback));

Other solutions are also good but I was binding multiple events in a loop and needed the self calling function to create an appropriate closure. Also, I did not want to disable the binding since I wanted it to be invoke-able on next click/touchstart.

Might help someone in similar situation!

Kaleidoscopic answered 21/2, 2014 at 2:46 Comment(0)
I
0

For simple features, just recognize touch or click I use the following code:

var element = $("#element");

element.click(function(e)
{
  if(e.target.ontouchstart !== undefined)
  {
    console.log( "touch" );
    return;
  }
  console.log( "no touch" );
});

This will return "touch" if the touchstart event is defined and "no touch" if not. Like I said this is a simple approach for click/tap events just that.

Instanter answered 22/3, 2014 at 5:20 Comment(0)
V
0

I am trying this and so far it works (but I am only on Android/Phonegap so caveat emptor)

  function filterEvent( ob, ev ) {
      if (ev.type == "touchstart") {
          ob.off('click').on('click', function(e){ e.preventDefault(); });
      }
  }
  $('#keypad').on('touchstart click', '.number, .dot', function(event) {
      filterEvent( $('#keypad'), event );
      console.log( event.type );  // debugging only
           ... finish handling touch events...
  }

I don't like the fact that I am re-binding handlers on every touch, but all things considered touches don't happen very often (in computer time!)

I have a TON of handlers like the one for '#keypad' so having a simple function that lets me deal with the problem without too much code is why I went this way.

Versatile answered 1/4, 2014 at 22:22 Comment(0)
C
0

EDIT: My former answer (based on answers in this thread) was not the way to go for me. I wanted a sub-menu to expand on mouse enter or touch click and to collapse on mouse leave or another touch click. Since mouse events normally are being fired after touch events, it was kind of tricky to write event listeners that support both touchscreen and mouse input at the same time.

jQuery plugin: Touch Or Mouse

I ended up writing a jQuery plugin called "Touch Or Mouse" (897 bytes minified) that can detect whether an event was invoked by a touchscreen or mouse (without testing for touch support!). This enables the support of both touchscreen and mouse at the same time and completely separate their events.

This way the OP can use touchstart or touchend for quickly responding to touch clicks and click for clicks invoked only by a mouse.

Demonstration

First one has to make ie. the body element track touch events:

$(document.body).touchOrMouse('init');

Mouse events our bound to elements in the default way and by calling $body.touchOrMouse('get', e) we can find out whether the event was invoked by a touchscreen or mouse.

$('.link').click(function(e) {
  var touchOrMouse = $(document.body).touchOrMouse('get', e);

  if (touchOrMouse === 'touch') {
    // Handle touch click.
  }
  else if (touchOrMouse === 'mouse') {
    // Handle mouse click.
  }
}

See the plugin at work at http://jsfiddle.net/lmeurs/uo4069nh.

Explanation

  1. This plugin needs to be called on ie. the body element to track touchstart and touchend events, this way the touchend event does not have to be fired on the trigger element (ie. a link or button). Between these two touch events this plugin considers any mouse event to be invoked by touch.
  2. Mouse events are fired only after touchend, when a mouse event is being fired within the ghostEventDelay (option, 1000ms by default) after touchend, this plugin considers the mouse event to be invoked by touch.
  3. When clicking on an element using a touchscreen, the element gains the :active state. The mouseleave event is only fired after the element loses this state by ie. clicking on another element. Since this could be seconds (or minutes!) after the mouseenter event has been fired, this plugin keeps track of an element's last mouseenter event: if the last mouseenter event was invoked by touch, the following mouseleave event is also considered to be invoked by touch.
Caviar answered 1/10, 2014 at 15:50 Comment(0)
Z
0

Try to use Virtual Mouse (vmouse) Bindings from jQuery Mobile. It's virtual event especially for your case:

$thing.on('vclick', function(event){ ... });

http://api.jquerymobile.com/vclick/

Browser support list: http://jquerymobile.com/browser-support/1.4/

Zinnes answered 16/2, 2015 at 12:59 Comment(0)
L
0

Here's a simple way to do it:

// A very simple fast click implementation
$thing.on('click touchstart', function(e) {
  if (!$(document).data('trigger')) $(document).data('trigger', e.type);
  if (e.type===$(document).data('trigger')) {
    // Do your stuff here
  }
});

You basically save the first event type that is triggered to the 'trigger' property in jQuery's data object that is attached to the root document, and only execute when the event type is equal to the value in 'trigger'. On touch devices, the event chain would likely be 'touchstart' followed by 'click'; however, the 'click' handler won't be executed because "click" doesn't match the initial event type saved in 'trigger' ("touchstart").

The assumption, and I do believe it's a safe one, is that your smartphone won't spontaneously change from a touch device to a mouse device or else the tap won't ever register because the 'trigger' event type is only saved once per page load and "click" would never match "touchstart".

Here's a codepen you can play around with (try tapping on the button on a touch device -- there should be no click delay): http://codepen.io/thdoan/pen/xVVrOZ

I also implemented this as a simple jQuery plugin that also supports jQuery's descendants filtering by passing a selector string:

// A very simple fast click plugin
// Syntax: .fastClick([selector,] handler)
$.fn.fastClick = function(arg1, arg2) {
  var selector, handler;
  switch (typeof arg1) {
    case 'function':
      selector = null;
      handler = arg1;
      break;
    case 'string':
      selector = arg1;
      if (typeof arg2==='function') handler = arg2;
      else return;
      break;
    default:
      return;
  }
  this.on('click touchstart', selector, function(e) {
    if (!$(document).data('trigger')) $(document).data('trigger', e.type);
    if (e.type===$(document).data('trigger')) handler.apply(this, arguments);
  });
};

Codepen: http://codepen.io/thdoan/pen/GZrBdo/

Loxodromics answered 9/3, 2016 at 8:9 Comment(2)
Why did you use a data property on the document instead of just using a variable?Hag
@RaphaelSchweikert Probably just out of habit. There's no right or wrong way, but I like to use $.data() to store data that can be accessed by multiple functions yet don't belong in the global scope. A plus is that since I'm storing it in an element it naturally provides me with more context.Loxodromics
B
0

UDPATE:

I've been working on an implementation to use both click and touchend events for the same function, and the function effectively blocks events if the type changes. My goal was to have a more responsive application interface - I wanted to reduce time for the event start to UI feedback loop.

For this implementation to work, the assumption is that you have all your related events added on 'click' and 'touchend'. This prevents one element from being deprived of event bubbling should both events be required to run, but are of different types.

Here is an lightweight API based implementation that I have simplified for demonstration purposes. It demonstrates how to use the functionality on a collapse element.

var tv = {
    /**
     * @method eventValidator()
     * @desc responsible for validating event of the same type.
     * @param {Object} e - event object
     * @param {Object} element - element event cache
     * @param {Function} callback - callback to invoke for events of the same type origin
     * @param {Object} [context] - context to pass to callback function
     * @param {Array} [args] - arguments array to pass in with context. Requires context to be passed
     * @return {Object} - new event cache
     */
    eventValidator: function(e, element, callback, context, args){
        if(element && element.type && element.type !== e.type){
            e.stopPropagation();
            e.preventDefault();
            return tv.createEventCacheObj({}, true);
        } else {
            element = tv.createEventCacheObj(e);
            (typeof context === "object" ? callback.apply(context, args) : callback());
            return element;
        }
    },

    /**
     * @method createEventCacheObj()
     * @param {Object} event - event object
     * @param {String} [event.type] - event type
     * @param {Number} [event.timeStamp] - time of event in MS since load
     * @param {Boolean} [reset=false] - flag to reset the object
     * @returns {{type: *, time: string}}
     */
    createEventCacheObj: function (event, reset){
        if(typeof reset !== 'boolean') reset = false;
        return {
            type: !reset ? event.type : null,
            time: !reset ? (event.timeStamp).toFixed(2): null
        };
    }
};

// Here is where the magic happens
var eventCache = [];
var pos = 0;

var $collapses = document.getElementsByClassName('tv-collapse__heading');
    Array.prototype.forEach.call($collapses, function(ele){
        ele.addEventListener('click', toggleCollapse);
        ele.addEventListener('touchend', toggleCollapse);

        // Cache mechanism
        ele.setAttribute('data-event-cache', String(pos++));
    });

/**
 * @func toggleCollapse()
 * @param {Object} e - event object
 * @desc responsible for toggling the state of a collapse element
 */
function toggleCollapse(e){
    eventCache[pos] = tv.eventValidator(e, eventCache[pos], function(){
       // Any event which isn't blocked will run the callback and its content
       // the context and arguments of the anonymous function match the event function context and arguments (assuming they are passed using the last two parameters of tv.eventValidator)

    }, this, arguments);
}

Original Answer:

Here is a response which is a modification of Rafael Fragoso's answer - pure JS.

(function(){
  
  button = document.getElementById('sayHi');
  
  button.addEventListener('touchstart', ohHai);
  button.addEventListener('click', ohHai);

  function ohHai(event){
    event.stopPropagation();
    event.preventDefault();
    console.log('ohHai is:', event.type);
  };
})();
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SO - Answer</title>
</head>
<body>
  <button id="sayHi">Anyone there?</button>
</body>
</html>

Run the snippet on the following and pay attention to the output:

  • Phone
  • Tablet
  • Tablet (Desktop Mode - if applicable)
  • Desktop
  • Desktop (Touch Screen - if applicable)

The key is we are stopping successive events from firing. Mobile browsers do their best to emulate a click when a touch happens. I wish I could find the link to an article a saw a while back that explains all the events that occur after touchstart through click. (I was searching for the 300ms delay between double tap and click actually firing).

Touch and Mouse Devices

I ran a couple of tests using a Surface Pro and a windows 10 desktop with a touchscreen. What I found was that they both triggered events as you would suspect, touchstart for touches and click for trackpad, mouse, and stylist. The interesting thing was that a touch event which was near, but not on the button, would triggering a click event without a touch event. It seems that the built in functionality in Windows 10 looks for the closest nodes within a radius and if a node is found it will fire a mouse based event.

Multiple Events of the Same Type

If two events of the same type are on an element, stopping the event from bubbling up could prevent one of the events from firing. There are a couple of different ways to handle this using some sort of cache. My initial thoughts were to modify the event object, but we get a reference so I'm thinking a cache solution will have to suffice.

Brunt answered 15/9, 2016 at 0:30 Comment(0)
I
0

The best method I have found is to write the touch event and have that event call the normal click event programatically. This way you have all your normal click events and then you need to add just one event handler for all touch events. For every node you want to make touchable, just add the "touchable" class to it to invoke the touch handler. With Jquery it works like so with some logic to make sure its a real touch event and not a false positive.

$("body").on("touchstart", ".touchable", function() { //make touchable  items fire like a click event
var d1 = new Date();
var n1 = d1.getTime();
setTimeout(function() {
    $(".touchable").on("touchend", function(event) {
        var d2 = new Date();
        var n2 = d2.getTime();
        if (n2 - n1 <= 300) {
            $(event.target).trigger("click"); //dont do the action here just call real click handler
        }
    });
}, 50)}).on("click", "#myelement", function() {
//all the behavior i originally wanted
});
Irreproachable answered 6/12, 2016 at 4:38 Comment(0)
R
0

find the document scroll move difference (both horizontal and vertical ) touchstart and touchend , if one of them is larger than 1 pixel, then it is move rather than click

var touchstartverscrollpos , touchstarthorscrollpos;


    $('body').on('touchstart','.thumbnail',function(e){

        touchstartverscrollpos = $(document).scrollTop();
        touchstarthorscrollpos = $(document).scrollLeft();


    });



    $('body').on('touchend','.thumbnail',function(e){


        var touchendverscrollpos = $(document).scrollTop();
        var touchendhorscrollpos = $(document).scrollLeft();

        var verdiff = touchendverscrollpos - touchstartverscrollpos;
        var hordiff = touchendhorscrollpos - touchstarthorscrollpos;


        if (Math.abs(verdiff) <1 && Math.abs(hordiff)<1){

// do you own function () here 



            e.stopImmediatePropagation();

            return false;
        }

    });
Rataplan answered 25/1, 2017 at 4:20 Comment(0)
S
0

I gave an answer there and I demonstrate with a jsfiddle. You can check for different devices and report it.

Basically I use a kind of event lock with some functions that serve it:

/*
 * Event lock functions
 * ====================
 */
function getEventLock(evt, key){
   if(typeof(eventLock[key]) == 'undefined'){
      eventLock[key] = {};
      eventLock[key].primary = evt.type;
      return true;
   }
   if(evt.type == eventLock[key].primary)
      return true;
   else
      return false;
}

function primaryEventLock(evt, key){
   eventLock[key].primary = evt.type;
}

Then, in my event handlers I start by a request to my lock:

/*
 * Event handlers
 * ==============
 */
$("#add").on("touchstart mousedown", addStart);
$("#add").on("touchend mouseup", addEnd);
function addStart(evt){
   // race condition between 'mousedown' and 'touchstart'
   if(!getEventLock(evt, 'add'))
      return;

   // some logic
   now = new Date().getTime();
   press = -defaults.pressDelay;
   task();

   // enable event lock and(?) event repetition
   pids.add = setTimeout(closure, defaults.pressDelay);

   function closure(){
        // some logic(?): comment out to disable repetition
      task();

      // set primary input device
      primaryEventLock(evt, 'add');

      // enable event repetition
      pids.add = setTimeout(closure, defaults.pressDelay);
   }
}
function addEnd(evt){
      clearTimeout(pids.add);
}

I have to stress that the problem is not to respond simply at a event but to NOT respond on both.

Finally, at jsfiddle there is a link to an updated version where I introduce minimal impact at existing code by adding just a simple call to my event lock library both at event start & end handlers along with 2 scope variables eventLock and eventLockDelay.

Swanskin answered 25/6, 2018 at 14:45 Comment(0)
C
0

If you're already using jQuery, there are two ways to handle this that are both really short and sweet. I don't think it needs to be as complicated as most of the answers...

  1. return false;

Simply listen for any/all events and add "return false;" at the end to stop additional duplicate event handlings.

thing$.on('click touchend',function(){
    // do Stuff
    return false; // stop
});

Reusable Example:

function bindClickAndTouchEvent(__targets$, __eventHandler){
    
    __targets$.on('click touchend',function(){
        __eventHandler.apply(this,arguments);// send original event
        return false;// but stop additional events
    });
}

// In-Line Usage:

bindClickAndTouchEvents( $('#some-element-id'), function(){ 
    console.log('Hey look only one click even when using touch on a touchscreen laptop') 
});
  1. Mouse Up and Touch End

Depending on your use case, you might just use mouseup and touchend, because those two events don't overlap at all and only create one event to begin with... then you won't even need "return false;".

thing$.on('mouseup touchend',function(){
    // do Stuff
});
Companionate answered 7/10, 2021 at 19:33 Comment(0)
A
-1

I'm not sure if this works on all browsers and devices. I tested this using Google Chrome and Safari iOS.

$thing.on('click || touchend', function(e){

});

The OR opperand should fire only the first event (on desktop that should be click and on an iPhone that should be touchend).

Awed answered 27/10, 2015 at 14:19 Comment(0)
S
-2

Instead of the timeout you could use a counter:

var count = 0;
$thing.bind('touchstart click', function(){
  count++;
  if (count %2 == 0) { //count 2% gives the remaining counts when devided by 2
    // do something
  }

  return false
});
Smock answered 9/11, 2012 at 16:29 Comment(3)
The % returns the remainder of dividing the 2 operands (var count and 2)Smock
The counter will not be incremented in certain cases, ie. after releasing the finger after moving the finger of of $thing.Caviar
This won't work if only one of either touchstart or click is fired. One click will take the counter to 1, then the following click wont // do somethingResolution

© 2022 - 2024 — McMap. All rights reserved.