Create a gist now

Instantly share code, notes, and snippets.

provides basics to handle touch actions on scrollable content
var ScrollHandler = (function(Math, UA){
// (C) 2014 - Andrea Giammarchi
// https://gist.github.com/WebReflection/8924001
var
MINIMUM_SCROLL_DISTANCE = 8,
ONE_EIGHTY_PI = 180 / Math.PI,
atan2 = Math.atan2,
round = Math.round,
sqrt = Math.sqrt,
ScrollHandlerPrototype = ScrollHandler.prototype,
// Android 2.X or 4.0 seems to not fire touchend/cancel
// once the DOM scrolls ... this might put
// the handler in a "never released state"
needsRecovery = /Android 2\./.test(UA) ||
/Android 4\.0/.test(UA),
// in such case a "hidden" property is used
timer = needsRecovery && '_' + Math.random(),
// to recover after 1 second of missing actions
clearTimer = needsRecovery && function(self) {
if (self[timer]) {
clearTimeout(self[timer]);
self[timer] = 0;
self._initialized = false;
}
}
;
function clearViaHandleEvent(e) {
events(e.currentTarget, 'removeEventListener', this);
}
function degrees(a, b) {
return atan2(
a.clientY - b.clientY,
a.clientX - b.clientX
) * ONE_EIGHTY_PI;
}
function distance(a, b) {
var x = a.clientX - b.clientX,
y = a.clientY - b.clientY;
return sqrt(x * x + y * y);
}
function events(el, method, handler) {
el[method]('touchstart', handler, true);
el[method]('touchend', handler, true);
el[method]('touchmove', handler, true);
el[method]('touchcancel', handler, true);
}
function nothingToDoHere(literally){}
function hscrolling(g) {
// SW NW
return (-45 < g && g < 45) ||
// NE NE
(135 < g && g <= 180) ||
// SE SE
(g < -135); //--> -180
}
function ScrollHandler(el, obj) {
if (!obj) obj = this;
el.classList.add('js-scroll-handler');
events(el, 'addEventListener', this);
this._initialized = false;
this.oninit = obj.oninit || nothingToDoHere;
this.onstart = obj.onstart || nothingToDoHere;
this.onscroll = obj.onscroll || nothingToDoHere;
this.onend = obj.onend || nothingToDoHere;
this.onclick = obj.onclick || nothingToDoHere;
}
ScrollHandlerPrototype.clear = function clear(e) {
this.handleEvent = clearViaHandleEvent;
if (e) {
this.handleEvent(e);
}
};
ScrollHandlerPrototype.handleEvent = function handleEvent(e) {
this[e.type](e);
};
ScrollHandlerPrototype.touchstart = function touchstart(e) {
var finger;
if (this._initialized === false) {
if (needsRecovery) {
this[timer] = setTimeout(clearTimer, 1000);
}
this._initialized = true;
finger = e.touches[0];
this.clientX = finger.clientX;
this.clientY = finger.clientY;
this.x = this.y = this.movedX = this.movedY = 0;
this.scrolling = this.vscrolling = this.hscrolling = false;
this.oninit(e);
}
};
ScrollHandlerPrototype.touchmove = function touchmove(e) {
var finger = e.touches[0],
px,
py;
if (needsRecovery) {
clearTimer(this);
}
if (!this.scrolling && MINIMUM_SCROLL_DISTANCE < distance(this, finger)) {
this.scrolling = true;
this.hscrolling = hscrolling(degrees(this, finger));
this.vscrolling = !this.hscrolling;
this.onstart(e);
}
if (this.scrolling) {
px = this.x;
py = this.y;
this.x = finger.clientX - this.clientX;
this.y = finger.clientY - this.clientY;
this.movedX = this.x - px;
this.movedY = this.y - py;
this.onscroll(e);
}
};
ScrollHandlerPrototype.touchend = function touchend(e) {
if (!e.touches.length) {
this.touchcancel(e);
if(!this.scrolling) {
this.onclick(e);
}
}
};
ScrollHandlerPrototype.touchcancel = function touchcancel(e) {
if (needsRecovery) {
clearTimer(this);
}
this._initialized = false;
if (this.scrolling) {
this.onend(e);
}
};
ScrollHandler.hasTimeoutRecovery = needsRecovery;
return ScrollHandler;
}(Math, navigator.userAgent));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment