jquery.finger
Version:
jQuery Finger unifies click and touch events by removing the 300ms delay on touch devices. It also provide a common set of events to handle basic gestures such as drag and pinch. Small (< 1kb gzipped), it is focused on performance, is well tested and ...
187 lines (156 loc) • 4.71 kB
JavaScript
/*
* jquery.finger
* https://github.com/ngryman/jquery.finger
*
* Copyright (c) 2013 ngryman
* Licensed under the MIT license.
*/
(function (factory) {
if (typeof define === 'function' && define.amd)
define(['jquery'], factory);
else if (typeof exports === 'object')
factory(require('jquery'));
else
factory(jQuery);
}(function ($) {
var ua = navigator.userAgent,
isChrome = /chrome/i.exec(ua),
isAndroid = /android/i.exec(ua),
hasTouch = 'ontouchstart' in window && !(isChrome && !isAndroid),
startEvent = hasTouch ? 'touchstart' : 'mousedown',
stopEvent = hasTouch ? 'touchend touchcancel' : 'mouseup mouseleave',
moveEvent = hasTouch ? 'touchmove' : 'mousemove',
namespace = 'finger',
rootEl = $('html')[0],
start = {},
move = {},
motion,
safeguard,
timeout,
prevEl,
prevTime,
Finger = $.Finger = {
pressDuration: 300,
doubleTapInterval: 300,
flickDuration: 150,
motionThreshold: 5
};
function preventDefault(event) {
event.preventDefault();
$.event.remove(rootEl, 'click', preventDefault);
}
function page(coord, event) {
return (hasTouch ? event.originalEvent.touches[0] : event)['page' + coord.toUpperCase()];
}
function trigger(event, evtName, remove) {
var fingerEvent = $.Event(evtName, move);
$.event.trigger(fingerEvent, { originalEvent: event }, event.target);
if (fingerEvent.isDefaultPrevented()) {
if (~evtName.indexOf('tap') && !hasTouch)
$.event.add(rootEl, 'click', preventDefault);
else
event.preventDefault();
}
if (remove) {
$.event.remove(rootEl, moveEvent + '.' + namespace, moveHandler);
$.event.remove(rootEl, stopEvent + '.' + namespace, stopHandler);
}
}
function startHandler(event) {
if (event.which > 1)
return;
var timeStamp = event.timeStamp || +new Date();
if (safeguard == timeStamp) return;
safeguard = timeStamp;
// initializes data
start.x = move.x = page('x', event);
start.y = move.y = page('y', event);
start.time = timeStamp;
start.target = event.target;
move.orientation = null;
move.end = false;
motion = false;
timeout = setTimeout(function() {
trigger(event, 'press', true);
}, Finger.pressDuration);
$.event.add(rootEl, moveEvent + '.' + namespace, moveHandler);
$.event.add(rootEl, stopEvent + '.' + namespace, stopHandler);
// global prevent default
if (Finger.preventDefault) {
event.preventDefault();
$.event.add(rootEl, 'click', preventDefault);
}
}
function moveHandler(event) {
// motion data
move.x = page('x', event);
move.y = page('y', event);
move.dx = move.x - start.x;
move.dy = move.y - start.y;
move.adx = Math.abs(move.dx);
move.ady = Math.abs(move.dy);
// security
motion = move.adx > Finger.motionThreshold || move.ady > Finger.motionThreshold;
if (!motion) return;
// moves cancel press events
clearTimeout(timeout);
// orientation
if (!move.orientation) {
if (move.adx > move.ady) {
move.orientation = 'horizontal';
move.direction = move.dx > 0 ? +1 : -1;
}
else {
move.orientation = 'vertical';
move.direction = move.dy > 0 ? +1 : -1;
}
}
// for delegated events, the target may change over time
// this ensures we notify the right target and simulates the mouseleave behavior
while (event.target && event.target !== start.target)
event.target = event.target.parentNode;
if (event.target !== start.target) {
event.target = start.target;
stopHandler.call(this, $.Event(stopEvent + '.' + namespace, event));
return;
}
// fire drag event
trigger(event, 'drag');
}
function stopHandler(event) {
var timeStamp = event.timeStamp || +new Date(),
dt = timeStamp - start.time,
evtName;
// always clears press timeout
clearTimeout(timeout);
// tap-like events
if (!motion) {
// triggered only if targets match
if (event.target === start.target) {
var doubleTap = prevEl === event.target && timeStamp - prevTime < Finger.doubleTapInterval;
evtName = doubleTap ? 'doubletap' : 'tap';
prevEl = doubleTap ? null : start.target;
prevTime = timeStamp;
}
}
// motion events
else {
// ensure last target is set the initial one
event.target = start.target;
if (dt < Finger.flickDuration) trigger(event, 'flick');
move.end = true;
evtName = 'drag';
}
if (evtName)
trigger(event, evtName, true);
}
// initial binding
$.event.add(rootEl, startEvent + '.' + namespace, startHandler);
// expose events as methods
$.each('tap doubletap press drag flick'.split(' '), function(i, name) {
$.fn[name] = function(fn) {
return fn ? this.on(name, fn) : this.trigger(name);
};
});
return Finger;
}));