Skip to content

Instantly share code, notes, and snippets.

@stardines
Forked from webcss/mithril-touch.js
Last active August 29, 2015 14:27
Show Gist options
  • Save stardines/d5eb90436a7e43fb20d5 to your computer and use it in GitHub Desktop.
Save stardines/d5eb90436a7e43fb20d5 to your computer and use it in GitHub Desktop.
mithril-touch, consume touch and mouse events evenly with mithril
/*****************************************
/* DOM touch support module
/*****************************************/
if (!window.CustomEvent) {
window.CustomEvent = function (event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
window.CustomEvent.prototype = window.Event.prototype;
}
(function(document) {
var TAPTRESHOLD = 200, // time within a double tap should have happend
TAPPRECISION = 60 / 2, // distance to identify a swipe gesture
touch = { },
tapCount = 0, // counts the number of touchstart events
tapTimer = 0, // timer to detect double tap
isTouchSwipe = false, // set to true whenever
absolute = Math.abs,
PANTRESHOLD = 5, // pan treshold
horizontalPanStates = 'ENDED', // States are BEGAN, PROGRESS, ENDED
verticalPanStates = 'ENDED',
panStartTime = 0,
panEndTime = 0,
touchSupported = 'ontouchstart' in window;
function parentIfText (node) {
return 'tagName' in node ? node : node.parentNode;
}
function dispatchEvent(type, touch) {
if(touchSupported) {
touch.originalEvent.preventDefault();
touch.originalEvent.stopImmediatePropagation();
}
var event = new CustomEvent(type, {
detail: touch,
bubbles: true,
cancelable: true
});
touch.target.dispatchEvent(event);
//console.log(type);
touch = { };
tapCount = 0;
return event;
}
function touchStart(e) {
if( !touchSupported || e.touches.length === 1) {
var coords = e.targetTouches ? e.targetTouches[0] : e;
touch = {
originalEvent: e,
target: parentIfText(e.target),
x1: coords.pageX,
y1: coords.pageY,
x2: coords.pageX,
y2: coords.pageY,
lastX: coords.pageX,
lastY: coords.pageY,
panDuration: 0
};
isTouchSwipe = false;
tapCount++;
if (!e.button || e.button === 1) {
clearTimeout(tapTimer);
tapTimer = setTimeout(function() {
if(absolute(touch.x2 - touch.x1) < TAPPRECISION &&
absolute(touch.y2 - touch.y2) < TAPPRECISION &&
!isTouchSwipe) {
dispatchEvent((tapCount===2)? 'dbltap' : 'tap', touch);
clearTimeout(tapTimer);
}
tapCount = 0;
}, TAPTRESHOLD);
}
}
}
function touchMove(e) {
var coords = e.changedTouches ? e.changedTouches[0] : e;
isTouchSwipe = true;
touch.x2 = coords.pageX;
touch.y2 = coords.pageY;
var distX = touch.lastX - touch.x2,
distY = touch.lastY - touch.y2,
absX = absolute(distX),
absY = absolute(distY);
touch.lastX = touch.x2;
touch.lastY = touch.y2;
if (absY > PANTRESHOLD && verticalPanStates === 'ENDED') {
touch.translation = distY;
panStartTime = Date.now();
dispatchEvent('verticalPanStart', touch);
verticalPanStates = 'PROGRESS';
}
if (absX > PANTRESHOLD && horizontalPanStates === 'ENDED') {
touch.translation = distX;
panStartTime = Date.now();
dispatchEvent('horizontalPanStart', touch);
horizontalPanStates = 'PROGRESS';
}
if(absY && verticalPanStates === 'PROGRESS') {
touch.translation = distY;
dispatchEvent('verticalPanChange', touch);
}
if(absX && horizontalPanStates === 'PROGRESS') {
touch.translation = distX;
dispatchEvent('horizontalPanChange', touch);
}
/* the following is obsolete since at least chrome handles this
// if movement is detected within 200ms from start, preventDefault to preserve browser scroll etc.
// if (touch.target &&
// (absolute(touch.y2 - touch.y1) <= TAPPRECISION ||
// absolute(touch.x2 - touch.x1) <= TAPPRECISION)
// ) {
// e.preventDefault();
// touchCancel(e);
// }
*/
}
function touchCancel(e) {
touch = {};
tapCount = 0;
isTouchSwipe = false;
console.log("touchCancel");
}
function touchEnd(e) {
var distX = touch.x2 - touch.x1,
distY = touch.y2 - touch.y1,
absX = absolute(distX),
absY = absolute(distY);
// use setTimeout here to register swipe over tap correctly,
// otherwise a tap would be fired immediatly after a swipe
setTimeout(function() {
isTouchSwipe = false;
},0);
panEndTime = Date.now();
touch.panDuration = (panStartTime - panEndTime) / 1000;
if (verticalPanStates === 'PROGRESS') {
touch.translation = distY;
dispatchEvent('verticalPanEnd', touch);
verticalPanStates = 'ENDED';
}
if (horizontalPanStates === 'PROGRESS') {
touch.translation = distX;
dispatchEvent('horizontalPanEnd', touch);
horizontalPanStates = 'ENDED';
}
// if there was swipe movement, resolve the direction of swipe
if(absX || absY) {
if(absX > absY) {
dispatchEvent((distX<0)? 'swipeleft': 'swiperight', touch);
} else {
dispatchEvent((distY<0)? 'swipeup': 'swipedown', touch);
}
}
}
document.addEventListener(touchSupported ? 'touchstart' : 'mousedown', touchStart, false);
document.addEventListener(touchSupported ? 'touchmove' : 'mousemove', touchMove, false);
document.addEventListener(touchSupported ? 'touchend' : 'mouseup', touchEnd, false);
// on touch devices, the taphold complies with contextmenu
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
dispatchEvent('taphold', {
originalEvent: e,
target: parentIfText(e.target)
});
}, false);
if (touchSupported) {
document.addEventListener('touchcancel', touchCancel, false);
}
}(window.document));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment