Best way to handle tap gestures I'm building a small web app primarily for mobile use. I have some widgets that need to handle tap gestures in the expected way, and I don't feel this one feature is reason enough to include JQuery mobile. 

I've written the following function for making an element respond to "tap" events:

function bind_tap(el, cb) {
  (function(){
    var touchStarted = false;
    el.addEventListener("click", cb, false);
    el.addEventListener("touchstart", function(){
      touchStarted = true;
    }, false);
    el.addEventListener("touchcancel", function(){
      touchStarted = false;
    }, false);
    el.addEventListener("touchleave", function(){
      touchStarted = false;
    }, false);
    el.addEventListener("touchend", function(){
      touchStarted = false;
      if (touchStarted) cb();
    }, false);
  })();
};


It seems to work fine, but I'm wondering if it could/should be improved in any way, either in that parts of it are redundant (I'm not sure what touchcancel really means) or to handle some edge case I haven't thought of.



Solution so far:

function bindTap(element, callback, useCapture) {
  var touchStarted = false, touchFinished = null;
  useCapture = !!useCapture;
  function startTap() {
      touchStarted = true;
      touchFinished = false;
  }
  function cancelTap() {
      touchStarted = false;
  }
  function completeTap() {
      if (touchStarted &amp;&amp; !touchFinished) {
          callback.apply(this, arguments);
          touchFinished = true;
      }
  }
  function handleClick() {
      touchStarted = true;
      completeTap.apply(this, arguments);
  }
  element.addEventListener("click", handleClick, useCapture);
  element.addEventListener("touchstart", startTap, useCapture);
  element.addEventListener("touchcancel", cancelTap, useCapture);
  element.addEventListener("touchleave", cancelTap, useCapture);
  element.addEventListener("touchend", completeTap, useCapture);
}
For your purposes, you can treat touchleave and touchcancel the same way. The difference is touchleave happens when the touch point leaves the element and moves somewhere else in the document, and touchcancel happens when the touch point leaves the document entirely, like moving outside the browser window's content area or onto a plugin.

Minor stuff:


The functions you use to handle touchleave and touchcancel are exactly the same, so you could eliminate redundancy by reusing the same function for both.
The third argument to addEventListner, useCapture, defaults to false, so you don't need to explicitly pass it in.
The IIFE inside of bind_tap serves no purpose, it can be removed.
JavaScript identifiers should use camelCase; bind_tap is not an appropriate name for an identifier.
Variables named like el and cb aren't a big deal for internal stuff, but for code that's exposed as part of your API (function parameters) they should probably have fully descriptive names.
You have an extra semicolon at the end of your code. Function declarations are not separated by (or terminated with) semicolons.


Major stuff:


In your touchend handler you set touchStarted to false and then check it for a truthy value on the next line; if (touchStarted) cb();. The callback will never fire there. The code is only working because you have the callback handling the click event. When you fix this, you'll need to make sure that the callback only fires once. I added touchFinished in the example below to address that.
You probably want to pass the this binding and any arguments on to the callback.




Something like this should work (not tested).

function bindTap(element, callback) {
    var touchStarted = false,
        touchFinished = false;

    function startTap() {
        touchStarted = true;
    }

    function cancelTap() {
        touchStarted = false;
    }

    function endTap() {
        if (touchStarted &amp;&amp; !touchFinished) {
            callback.apply(this, arguments);
            touchFinished = true;
        }
    }

    function handleClick() {
        touchStarted = true;
        endTap.apply(this, arguments);
    }

    element.addEventListener("click", handleClick);
    element.addEventListener("touchstart", startTap);
    element.addEventListener("touchcancel", cancelTap);
    element.addEventListener("touchleave", cancelTap);
    element.addEventListener("touchend", endTap);
}