provides basics to handle touch actions on scrollable content
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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