Last Updated: February 25, 2016
·
6.167K
· denizac

Easily bind `click` event handlers to `tap` events

Browser click events on touch-oriented devices carry with them a sluggish 300ms delay. This extension to jQuery (or Zepto) intercepts click event handlers and turns them into tap event handlers so you can respond to user input faster.

But wait! After your tap handler fires, a click event will be fired at the same x, y coordinates 300ms later. This extension will block the resulting click event if the tap handler has fired.

/*global define: false, window: false */
define(['jquery'], function ($) {
  'use strict';

  return function () {
    /* Begin monkey-patch Tap event support into $ */
    var x = 0,
      y = 0,
      threshold = 40,
      // Sometimes there is lag between the last update and touchend.
      // Unfortunately, we can't get coords on touchend, so this is
      // the fudge factor.

      indexOfTap = function(x, y) {
        var i, len, tapX, tapY;
        for(i = 0, len = taps.length; i < len; i++) {
          tapX = taps[i][0];
          tapY = taps[i][1];
          if (Math.abs(tapX - x) <= threshold &&
            Math.abs(tapY - y) <= threshold) {
            return i;
          }
        }
        return -1;
      },
      makeTap = function (callback) {
        return function (e) {
          return callback.call(this, e);
        };
      },
      wrapClick = function (callback) {
        return function (e) {

          var tap = indexOfTap(e.x, e.y);
          if (tap !== -1) {
            taps.splice(tap, 1); // remove the tap
            e.preventDefault();
            e.stopPropagation();
            return;
          }

          return callback.call(this, e);
        };
      };

      var taps = [];


    try {
      var update = function (e) {
        x = e.touches[0].clientX;
        y = e.touches[0].clientY;
      };
      window.addEventListener('touchstart', update, true);
      window.addEventListener('touchmove', update, true);
      window.addEventListener('touchend', function (e) {
        taps.push([x,y]);
      }, true);

    } catch (o_O){
      //could not register listeners
    }

    $.fn.oldBind = $.fn.bind;
    $.fn.oldDelegate = $.fn.delegate;
    $.fn.bind = function (event, callback) {
      if (event.indexOf('click') > -1) {
        this.oldBind('tap', makeTap(callback));
        this.oldBind(event, wrapClick(callback));
      } else {
        this.oldBind(event, callback);
      }

      return this;
    };

    $.fn.delegate = function (selector, event, callback) {
      if (event.indexOf('click') > -1) {
        this.oldDelegate(selector, 'tap', makeTap(callback));
        this.oldDelegate(selector, event, wrapClick(callback));
      } else {
        this.oldDelegate(selector, event, callback);
      }

      return this;
    };

    /* end monkey-patch Tap event support into $  */
  };

});

See the gist at https://gist.github.com/3702931

3 Responses
Add your response

over 1 year ago ·

@mlb I would be interested in seeing these 2 methods benchmarked against each other

over 1 year ago ·

Awesome, thanks for this. Had problems with the tap event in jquery mobile that this could solve.

over 1 year ago ·