Last Updated: February 25, 2016
·
6.458K
· 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 ·

Have a fresh tip? Share with Coderwall community!

Post
Post a tip
Did you know?

JavaScript on a page runs with the privileges of that origin. One altered library, tag manager, or snippet can read inputs, change what people see, and call your APIs as the user.

Security insights from Cside