123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- /**
- * energize.js v0.1.0
- *
- * Speeds up click events on mobile devices.
- * https://github.com/davidcalhoun/energize.js
- */
- (function() { // Sandbox
- /**
- * Don't add to non-touch devices, which don't need to be sped up
- */
- if(!('ontouchstart' in window)) return;
- var lastClick = {},
- isThresholdReached, touchstart, touchmove, touchend,
- click, closest;
-
- /**
- * isThresholdReached
- *
- * Compare touchstart with touchend xy coordinates,
- * and only fire simulated click event if the coordinates
- * are nearby. (don't want clicking to be confused with a swipe)
- */
- isThresholdReached = function(startXY, xy) {
- return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5;
- };
- /**
- * touchstart
- *
- * Save xy coordinates when the user starts touching the screen
- */
- touchstart = function(e) {
- this.startXY = [e.touches[0].clientX, e.touches[0].clientY];
- this.threshold = false;
- };
- /**
- * touchmove
- *
- * Check if the user is scrolling past the threshold.
- * Have to check here because touchend will not always fire
- * on some tested devices (Kindle Fire?)
- */
- touchmove = function(e) {
- // NOOP if the threshold has already been reached
- if(this.threshold) return false;
- this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]);
- };
- /**
- * touchend
- *
- * If the user didn't scroll past the threshold between
- * touchstart and touchend, fire a simulated click.
- *
- * (This will fire before a native click)
- */
- touchend = function(e) {
- // Don't fire a click if the user scrolled past the threshold
- if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) {
- return;
- }
-
- /**
- * Create and fire a click event on the target element
- * https://developer.mozilla.org/en/DOM/event.initMouseEvent
- */
- var touch = e.changedTouches[0],
- evt = document.createEvent('MouseEvents');
- evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
- evt.simulated = true; // distinguish from a normal (nonsimulated) click
- e.target.dispatchEvent(evt);
- };
-
- /**
- * click
- *
- * Because we've already fired a click event in touchend,
- * we need to listed for all native click events here
- * and suppress them as necessary.
- */
- click = function(e) {
- /**
- * Prevent ghost clicks by only allowing clicks we created
- * in the click event we fired (look for e.simulated)
- */
- var time = Date.now(),
- timeDiff = time - lastClick.time,
- x = e.clientX,
- y = e.clientY,
- xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)],
- target = closest(e.target, 'A') || e.target, // needed for standalone apps
- nodeName = target.nodeName,
- isLink = nodeName === 'A',
- standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href");
-
- lastClick.time = time;
- lastClick.x = x;
- lastClick.y = y;
- /**
- * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire),
- * so we have to add more logic to determine the time of the last click. Not perfect...
- *
- * Older, simpler check: if((!e.simulated) || standAlone)
- */
- if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) {
- e.preventDefault();
- e.stopPropagation();
- if(!standAlone) return false;
- }
- /**
- * Special logic for standalone web apps
- * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window
- */
- if(standAlone) {
- window.location = target.getAttribute("href");
- }
- /**
- * Add an energize-focus class to the targeted link (mimics :focus behavior)
- * TODO: test and/or remove? Does this work?
- */
- if(!target || !target.classList) return;
- target.classList.add("energize-focus");
- window.setTimeout(function(){
- target.classList.remove("energize-focus");
- }, 150);
- };
- /**
- * closest
- * @param {HTMLElement} node current node to start searching from.
- * @param {string} tagName the (uppercase) name of the tag you're looking for.
- *
- * Find the closest ancestor tag of a given node.
- *
- * Starts at node and goes up the DOM tree looking for a
- * matching nodeName, continuing until hitting document.body
- */
- closest = function(node, tagName){
- var curNode = node;
- while(curNode !== document.body) { // go up the dom until we find the tag we're after
- if(!curNode || curNode.nodeName === tagName) { return curNode; } // found
- curNode = curNode.parentNode; // not found, so keep going up
- }
-
- return null; // not found
- };
- /**
- * Add all delegated event listeners
- *
- * All the events we care about bubble up to document,
- * so we can take advantage of event delegation.
- *
- * Note: no need to wait for DOMContentLoaded here
- */
- document.addEventListener('touchstart', touchstart, false);
- document.addEventListener('touchmove', touchmove, false);
- document.addEventListener('touchend', touchend, false);
- document.addEventListener('click', click, true); // TODO: why does this use capture?
-
- })();
|