(function() { /** * From: http://code.this.com/mobile/articles/fast_buttons.html * Also see: http://stackoverflow.com/questions/6300136/trying-to-implement-googles-fast-button */ /** For IE8 and earlier compatibility: https://developer.mozilla.org/en/DOM/element.addEventListener */ function addListener(el, type, listener, useCapture) { if (el.addEventListener) { el.addEventListener(type, listener, useCapture); return { destroy: function() { el.removeEventListener(type, listener, useCapture); } }; } else { // see: http://stackoverflow.com/questions/5198845/javascript-this-losing-context-in-ie var handler = function(e) { listener.handleEvent(window.event, listener); } el.attachEvent('on' + type, handler); return { destroy: function() { el.detachEvent('on' + type, handler); } }; } } var isTouch = "ontouchstart" in window; /* Construct the FastButton with a reference to the element and click handler. */ this.FastButton = function(element, handler, useCapture) { // collect functions to call to cleanup events this.events = []; this.touchEvents = []; this.element = element; this.handler = handler; this.useCapture = useCapture; if (isTouch) this.events.push(addListener(element, 'touchstart', this, this.useCapture)); this.events.push(addListener(element, 'click', this, this.useCapture)); }; /* Remove event handling when no longer needed for this button */ this.FastButton.prototype.destroy = function() { for (i = this.events.length - 1; i >= 0; i -= 1) this.events[i].destroy(); this.events = this.touchEvents = this.element = this.handler = this.fastButton = null; }; /* acts as an event dispatcher */ this.FastButton.prototype.handleEvent = function(event) { switch (event.type) { case 'touchstart': this.onTouchStart(event); break; case 'touchmove': this.onTouchMove(event); break; case 'touchend': this.onClick(event); break; case 'click': this.onClick(event); break; } }; /* Save a reference to the touchstart coordinate and start listening to touchmove and touchend events. Calling stopPropagation guarantees that other behaviors don’t get a chance to handle the same click event. This is executed at the beginning of touch. */ this.FastButton.prototype.onTouchStart = function(event) { event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); this.touchEvents.push(addListener(this.element, 'touchend', this, this.useCapture)); this.touchEvents.push(addListener(document.body, 'touchmove', this, this.useCapture)); this.startX = event.touches[0].clientX; this.startY = event.touches[0].clientY; }; /* When /if touchmove event is invoked, check if the user has dragged past the threshold of 10px. */ this.FastButton.prototype.onTouchMove = function(event) { if (Math.abs(event.touches[0].clientX - this.startX) > 10 || Math.abs(event.touches[0].clientY - this.startY) > 10) { this.reset(); //if he did, then cancel the touch event } }; /* Invoke the actual click handler and prevent ghost clicks if this was a touchend event. */ this.FastButton.prototype.onClick = function(event) { event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); this.reset(); // Use .call to call the method so that we have the correct "this": https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call var result = this.handler.call(this.element, event); if (event.type == 'touchend') clickbuster.preventGhostClick(this.startX, this.startY); return result; }; this.FastButton.prototype.reset = function() { for (i = this.touchEvents.length - 1; i >= 0; i -= 1) this.touchEvents[i].destroy(); this.touchEvents = []; }; this.clickbuster = function() {} /* Call preventGhostClick to bust all click events that happen within 25px of the provided x, y coordinates in the next 2.5s. */ this.clickbuster.preventGhostClick = function(x, y) { clickbuster.coordinates.push(x, y); window.setTimeout(clickbuster.pop, 2500); }; this.clickbuster.pop = function() { clickbuster.coordinates.splice(0, 2); }; /* If we catch a click event inside the given radius and time threshold then we call stopPropagation and preventDefault. Calling preventDefault will stop links from being activated. */ this.clickbuster.onClick = function(event) { for (var i = 0; i < clickbuster.coordinates.length; i += 2) { var x = clickbuster.coordinates[i]; var y = clickbuster.coordinates[i + 1]; if (Math.abs(event.clientX - x) < 25 && Math.abs(event.clientY - y) < 25) { event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); event.preventDefault ? event.preventDefault() : (event.returnValue=false); } } }; if (isTouch) { // Don't need to use our custom addListener function since we only bust clicks on touch devices document.addEventListener('click', clickbuster.onClick, true); clickbuster.coordinates = []; } })(this);