add fastclick v0.3.x

This commit is contained in:
Peter Dave Hello
2014-09-14 17:11:05 +08:00
parent be1b17f17c
commit ace01be3cf
6 changed files with 1116 additions and 0 deletions
+366
View File
@@ -0,0 +1,366 @@
/**
* @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
*
* @version 0.3.2
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
*/
/*jslint browser:true*/
/*global Node, define*/
/**
* Instantiate fast-clicking listeners on the specificed layer.
*
* @constructor
* @param {Element} layer The layer to listen on
*/
function FastClick(layer) {
'use strict';
var oldOnClick, that = this;
/**
* Whether a click is currently being tracked.
*
* @type boolean
*/
this.trackingClick = false,
/**
* The element being tracked for a click.
*
* @type Element
*/
this.targetElement = null;
/**
* The FastClick layer.
*
* @type Element
*/
this.layer = layer;
if (!layer || !layer.nodeType) {
throw new TypeError('Layer must be a document node');
}
// Bind handlers to this instance
this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); };
this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); };
this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); };
this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); };
this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); };
// Devices that don't support touch don't need FastClick
if (typeof window.ontouchstart === 'undefined') {
return;
}
// Set up event handlers as required
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, true);
layer.addEventListener('touchmove', this.onTouchMove, true);
layer.addEventListener('touchend', this.onTouchEnd, true);
layer.addEventListener('touchcancel', this.onTouchCancel, true);
// If a handler is already declared in the element's onclick attribute, it will be fired before
// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
// adding it as listener.
if (typeof layer.onclick === 'function') {
// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
// - the old one won't work if passed to addEventListener directly.
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
/**
* Android requires an exception for labels.
*
* @type boolean
*/
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
/**
* Determine whether a given element requires a native click.
*
* @param {Element} target DOM element
* @returns {boolean} Returns true if the element needs a native click
*/
FastClick.prototype.needsClick = function(target) {
'use strict';
switch (target.nodeName.toLowerCase()) {
case 'label':
case 'video':
return true;
default:
return (/\bneedsclick\b/).test(target.className);
}
};
/**
* Determine whether a given element requires a call to focus to simulate click into element.
*
* @param {Element} target target DOM element.
* @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
*/
FastClick.prototype.needsFocus = function(target) {
'use strict';
switch(target.nodeName.toLowerCase()) {
case 'textarea':
case 'select':
return true;
case 'input':
switch (target.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
default:
return true;
}
break;
default:
return (/\bneedsfocus\b/).test(target.className);
}
};
/**
* Send a click event to the element if it needs it.
*
* @returns {boolean} Whether the click was sent or not
*/
FastClick.prototype.maybeSendClick = function(targetElement, event) {
'use strict';
var clickEvent, touch;
// Prevent the actual click from going though - unless the target node is marked as requiring
// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted
// to open the options list and so the original event is required.
if (this.needsClick(targetElement)) {
return false;
}
touch = event.changedTouches[0];
// Synthesise a click event, with an extra attribute so it can be tracked
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
return true;
};
/**
* On touch start, record the position and scroll offset.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchStart = function(event) {
'use strict';
var touch = event.targetTouches[0];
this.trackingClick = true;
this.targetElement = event.target;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
return true;
};
/**
* Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.touchHasMoved = function(event) {
'use strict';
var touch = event.targetTouches[0];
if (Math.abs(touch.pageX - this.touchStartX) > 10
|| Math.abs(touch.pageY - this.touchStartY) > 10) {
return true;
}
return false;
};
/**
* Update the last position.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchMove = function(event) {
'use strict';
if (!this.trackingClick) {
return true;
}
// If the touch has moved, cancel the click tracking
if (this.targetElement !== event.target || this.touchHasMoved(event)) {
this.trackingClick = false;
this.targetElement = null;
}
return true;
};
/**
* On touch end, determine whether to send a click event at once.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchEnd = function(event) {
'use strict';
var forElement, targetElement = this.targetElement;
if (!this.trackingClick) {
return true;
}
this.trackingClick = false;
if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) {
forElement = document.getElementById(targetElement.htmlFor);
if (forElement) {
targetElement.focus();
if (this.android) {
return false;
}
if (this.maybeSendClick(forElement, event)) {
event.preventDefault();
}
return false;
}
} else if (this.needsFocus(targetElement)) {
targetElement.focus();
if (targetElement.tagName.toLowerCase() !== 'select') {
event.preventDefault();
}
return false;
}
if (!this.maybeSendClick(targetElement, event)) {
return false;
}
event.preventDefault();
return false;
};
/**
* On touch cancel, stop tracking the click.
*
* @returns {void}
*/
FastClick.prototype.onTouchCancel = function() {
'use strict';
this.trackingClick = false;
this.targetElement = null;
};
/**
* On actual clicks, determine whether this is a touch-generated click, a click action occurring
* naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
* an actual click which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onClick = function(event) {
'use strict';
var oldTargetElement;
if (event.forwardedTouchEvent) {
return true;
}
// If a target element was never set (because a touch event was never fired) allow the click
if (!this.targetElement) {
return true;
}
oldTargetElement = this.targetElement;
this.targetElement = null;
// Programmatically generated events targeting a specific element should be permitted
if (!event.cancelable) {
return true;
}
// Derive and check the target element to see whether the click needs to be permitted;
// unless explicitly enabled, prevent non-touch click events from triggering actions,
// to prevent ghost/doubleclicks.
if (!this.needsClick(oldTargetElement)) {
// Prevent any user-added listeners declared on FastClick element from being fired.
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
}
// Cancel the event
event.stopPropagation();
event.preventDefault();
return false;
}
// If clicks are permitted, return true for the action to go through.
return true;
};
/**
* Remove all FastClick's event listeners.
*
* @returns {void}
*/
FastClick.prototype.destroy = function() {
'use strict';
var layer = this.layer;
layer.removeEventListener('click', this.onClick, true);
layer.removeEventListener('touchstart', this.onTouchStart, true);
layer.removeEventListener('touchmove', this.onTouchMove, true);
layer.removeEventListener('touchend', this.onTouchEnd, true);
layer.removeEventListener('touchcancel', this.onTouchCancel, true);
};
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(function() {
'use strict';
return FastClick;
});
}
+1
View File
@@ -0,0 +1 @@
function FastClick(t){"use strict";var e,n=this;if(this.trackingClick=!1,this.targetElement=null,this.layer=t,!t||!t.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){FastClick.prototype.onClick.apply(n,arguments)},this.onTouchStart=function(){FastClick.prototype.onTouchStart.apply(n,arguments)},this.onTouchMove=function(){FastClick.prototype.onTouchMove.apply(n,arguments)},this.onTouchEnd=function(){FastClick.prototype.onTouchEnd.apply(n,arguments)},this.onTouchCancel=function(){FastClick.prototype.onTouchCancel.apply(n,arguments)},"undefined"!=typeof window.ontouchstart&&(t.addEventListener("click",this.onClick,!0),t.addEventListener("touchstart",this.onTouchStart,!0),t.addEventListener("touchmove",this.onTouchMove,!0),t.addEventListener("touchend",this.onTouchEnd,!0),t.addEventListener("touchcancel",this.onTouchCancel,!0),"function"==typeof t.onclick&&(e=t.onclick,t.addEventListener("click",function(t){e(t)},!1),t.onclick=null))}FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.needsClick=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"label":case"video":return!0;default:return/\bneedsclick\b/.test(t.className)}},FastClick.prototype.needsFocus=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(t.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1;default:return!0}break;default:return/\bneedsfocus\b/.test(t.className)}},FastClick.prototype.maybeSendClick=function(t,e){"use strict";var n,o;return this.needsClick(t)?!1:(o=e.changedTouches[0],n=document.createEvent("MouseEvents"),n.initMouseEvent("click",!0,!0,window,1,o.screenX,o.screenY,o.clientX,o.clientY,!1,!1,!1,!1,0,null),n.forwardedTouchEvent=!0,t.dispatchEvent(n),!0)},FastClick.prototype.onTouchStart=function(t){"use strict";var e=t.targetTouches[0];return this.trackingClick=!0,this.targetElement=t.target,this.touchStartX=e.pageX,this.touchStartY=e.pageY,!0},FastClick.prototype.touchHasMoved=function(t){"use strict";var e=t.targetTouches[0];return Math.abs(e.pageX-this.touchStartX)>10||Math.abs(e.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.onTouchMove=function(t){"use strict";return this.trackingClick?((this.targetElement!==t.target||this.touchHasMoved(t))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.onTouchEnd=function(t){"use strict";var e,n=this.targetElement;if(!this.trackingClick)return!0;if(this.trackingClick=!1,"label"===n.nodeName.toLowerCase()&&n.htmlFor){if(e=document.getElementById(n.htmlFor))return n.focus(),this.android?!1:(this.maybeSendClick(e,t)&&t.preventDefault(),!1)}else if(this.needsFocus(n))return n.focus(),"select"!==n.tagName.toLowerCase()&&t.preventDefault(),!1;return this.maybeSendClick(n,t)?(t.preventDefault(),!1):!1},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onClick=function(t){"use strict";var e;return t.forwardedTouchEvent?!0:this.targetElement?(e=this.targetElement,this.targetElement=null,t.cancelable?this.needsClick(e)?!0:(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault(),!1):!0):!0},FastClick.prototype.destroy=function(){"use strict";var t=this.layer;t.removeEventListener("click",this.onClick,!0),t.removeEventListener("touchstart",this.onTouchStart,!0),t.removeEventListener("touchmove",this.onTouchMove,!0),t.removeEventListener("touchend",this.onTouchEnd,!0),t.removeEventListener("touchcancel",this.onTouchCancel,!0)},"function"==typeof define&&define.amd&&define(function(){"use strict";return FastClick});
+371
View File
@@ -0,0 +1,371 @@
/**
* @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
*
* @version 0.3.3
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
*/
/*jslint browser:true*/
/*global Node, define*/
/**
* Instantiate fast-clicking listeners on the specificed layer.
*
* @constructor
* @param {Element} layer The layer to listen on
*/
function FastClick(layer) {
'use strict';
var oldOnClick, that = this;
/**
* Whether a click is currently being tracked.
*
* @type boolean
*/
this.trackingClick = false,
/**
* The element being tracked for a click.
*
* @type Element
*/
this.targetElement = null;
/**
* The FastClick layer.
*
* @type Element
*/
this.layer = layer;
if (!layer || !layer.nodeType) {
throw new TypeError('Layer must be a document node');
}
// Bind handlers to this instance
this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); };
this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); };
this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); };
this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); };
this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); };
// Devices that don't support touch don't need FastClick
if (typeof window.ontouchstart === 'undefined') {
return;
}
// Set up event handlers as required
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, true);
layer.addEventListener('touchmove', this.onTouchMove, true);
layer.addEventListener('touchend', this.onTouchEnd, true);
layer.addEventListener('touchcancel', this.onTouchCancel, true);
// If a handler is already declared in the element's onclick attribute, it will be fired before
// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
// adding it as listener.
if (typeof layer.onclick === 'function') {
// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
// - the old one won't work if passed to addEventListener directly.
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
/**
* Android requires an exception for labels.
*
* @type boolean
*/
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
/**
* Determine whether a given element requires a native click.
*
* @param {Element} target DOM element
* @returns {boolean} Returns true if the element needs a native click
*/
FastClick.prototype.needsClick = function(target) {
'use strict';
switch (target.nodeName.toLowerCase()) {
case 'label':
case 'video':
return true;
default:
return (/\bneedsclick\b/).test(target.className);
}
};
/**
* Determine whether a given element requires a call to focus to simulate click into element.
*
* @param {Element} target target DOM element.
* @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
*/
FastClick.prototype.needsFocus = function(target) {
'use strict';
switch(target.nodeName.toLowerCase()) {
case 'textarea':
case 'select':
return true;
case 'input':
switch (target.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
default:
return true;
}
break;
default:
return (/\bneedsfocus\b/).test(target.className);
}
};
/**
* Send a click event to the element if it needs it.
*
* @returns {boolean} Whether the click was sent or not
*/
FastClick.prototype.maybeSendClick = function(targetElement, event) {
'use strict';
var clickEvent, touch;
// Prevent the actual click from going though - unless the target node is marked as requiring
// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted
// to open the options list and so the original event is required.
if (this.needsClick(targetElement)) {
return false;
}
touch = event.changedTouches[0];
// Synthesise a click event, with an extra attribute so it can be tracked
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
return true;
};
/**
* On touch start, record the position and scroll offset.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchStart = function(event) {
'use strict';
var touch = event.targetTouches[0];
this.trackingClick = true;
this.targetElement = event.target;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
return true;
};
/**
* Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.touchHasMoved = function(event) {
'use strict';
var touch = event.targetTouches[0];
if (Math.abs(touch.pageX - this.touchStartX) > 10
|| Math.abs(touch.pageY - this.touchStartY) > 10) {
return true;
}
return false;
};
/**
* Update the last position.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchMove = function(event) {
'use strict';
if (!this.trackingClick) {
return true;
}
// If the touch has moved, cancel the click tracking
if (this.targetElement !== event.target || this.touchHasMoved(event)) {
this.trackingClick = false;
this.targetElement = null;
}
return true;
};
/**
* On touch end, determine whether to send a click event at once.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchEnd = function(event) {
'use strict';
var forElement, targetElement = this.targetElement;
if (!this.trackingClick) {
return true;
}
this.trackingClick = false;
if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) {
forElement = document.getElementById(targetElement.htmlFor);
if (forElement) {
targetElement.focus();
if (this.android) {
return false;
}
if (this.maybeSendClick(forElement, event)) {
event.preventDefault();
}
return false;
}
} else if (this.needsFocus(targetElement)) {
targetElement.focus();
if (targetElement.tagName.toLowerCase() !== 'select') {
event.preventDefault();
}
return false;
}
if (!this.maybeSendClick(targetElement, event)) {
return false;
}
event.preventDefault();
return false;
};
/**
* On touch cancel, stop tracking the click.
*
* @returns {void}
*/
FastClick.prototype.onTouchCancel = function() {
'use strict';
this.trackingClick = false;
this.targetElement = null;
};
/**
* On actual clicks, determine whether this is a touch-generated click, a click action occurring
* naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
* an actual click which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onClick = function(event) {
'use strict';
var oldTargetElement;
if (event.forwardedTouchEvent) {
return true;
}
// If a target element was never set (because a touch event was never fired) allow the click
if (!this.targetElement) {
return true;
}
oldTargetElement = this.targetElement;
this.targetElement = null;
// Programmatically generated events targeting a specific element should be permitted
if (!event.cancelable) {
return true;
}
// Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
// Derive and check the target element to see whether the click needs to be permitted;
// unless explicitly enabled, prevent non-touch click events from triggering actions,
// to prevent ghost/doubleclicks.
if (!this.needsClick(oldTargetElement)) {
// Prevent any user-added listeners declared on FastClick element from being fired.
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
}
// Cancel the event
event.stopPropagation();
event.preventDefault();
return false;
}
// If clicks are permitted, return true for the action to go through.
return true;
};
/**
* Remove all FastClick's event listeners.
*
* @returns {void}
*/
FastClick.prototype.destroy = function() {
'use strict';
var layer = this.layer;
layer.removeEventListener('click', this.onClick, true);
layer.removeEventListener('touchstart', this.onTouchStart, true);
layer.removeEventListener('touchmove', this.onTouchMove, true);
layer.removeEventListener('touchend', this.onTouchEnd, true);
layer.removeEventListener('touchcancel', this.onTouchCancel, true);
};
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(function() {
'use strict';
return FastClick;
});
}
+1
View File
@@ -0,0 +1 @@
function FastClick(t){"use strict";var e,n=this;if(this.trackingClick=!1,this.targetElement=null,this.layer=t,!t||!t.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){FastClick.prototype.onClick.apply(n,arguments)},this.onTouchStart=function(){FastClick.prototype.onTouchStart.apply(n,arguments)},this.onTouchMove=function(){FastClick.prototype.onTouchMove.apply(n,arguments)},this.onTouchEnd=function(){FastClick.prototype.onTouchEnd.apply(n,arguments)},this.onTouchCancel=function(){FastClick.prototype.onTouchCancel.apply(n,arguments)},"undefined"!=typeof window.ontouchstart&&(t.addEventListener("click",this.onClick,!0),t.addEventListener("touchstart",this.onTouchStart,!0),t.addEventListener("touchmove",this.onTouchMove,!0),t.addEventListener("touchend",this.onTouchEnd,!0),t.addEventListener("touchcancel",this.onTouchCancel,!0),"function"==typeof t.onclick&&(e=t.onclick,t.addEventListener("click",function(t){e(t)},!1),t.onclick=null))}FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.needsClick=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"label":case"video":return!0;default:return/\bneedsclick\b/.test(t.className)}},FastClick.prototype.needsFocus=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(t.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1;default:return!0}break;default:return/\bneedsfocus\b/.test(t.className)}},FastClick.prototype.maybeSendClick=function(t,e){"use strict";var n,o;return this.needsClick(t)?!1:(o=e.changedTouches[0],n=document.createEvent("MouseEvents"),n.initMouseEvent("click",!0,!0,window,1,o.screenX,o.screenY,o.clientX,o.clientY,!1,!1,!1,!1,0,null),n.forwardedTouchEvent=!0,t.dispatchEvent(n),!0)},FastClick.prototype.onTouchStart=function(t){"use strict";var e=t.targetTouches[0];return this.trackingClick=!0,this.targetElement=t.target,this.touchStartX=e.pageX,this.touchStartY=e.pageY,!0},FastClick.prototype.touchHasMoved=function(t){"use strict";var e=t.targetTouches[0];return Math.abs(e.pageX-this.touchStartX)>10||Math.abs(e.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.onTouchMove=function(t){"use strict";return this.trackingClick?((this.targetElement!==t.target||this.touchHasMoved(t))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.onTouchEnd=function(t){"use strict";var e,n=this.targetElement;if(!this.trackingClick)return!0;if(this.trackingClick=!1,"label"===n.nodeName.toLowerCase()&&n.htmlFor){if(e=document.getElementById(n.htmlFor))return n.focus(),this.android?!1:(this.maybeSendClick(e,t)&&t.preventDefault(),!1)}else if(this.needsFocus(n))return n.focus(),"select"!==n.tagName.toLowerCase()&&t.preventDefault(),!1;return this.maybeSendClick(n,t)?(t.preventDefault(),!1):!1},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onClick=function(t){"use strict";var e;return t.forwardedTouchEvent?!0:this.targetElement?(e=this.targetElement,this.targetElement=null,t.cancelable?"submit"===t.target.type&&0===t.detail?!0:this.needsClick(e)?!0:(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault(),!1):!0):!0},FastClick.prototype.destroy=function(){"use strict";var t=this.layer;t.removeEventListener("click",this.onClick,!0),t.removeEventListener("touchstart",this.onTouchStart,!0),t.removeEventListener("touchmove",this.onTouchMove,!0),t.removeEventListener("touchend",this.onTouchEnd,!0),t.removeEventListener("touchcancel",this.onTouchCancel,!0)},"function"==typeof define&&define.amd&&define(function(){"use strict";return FastClick});
+376
View File
@@ -0,0 +1,376 @@
/**
* @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
*
* @version 0.3.5
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
*/
/*jslint browser:true*/
/*global define*/
/**
* Instantiate fast-clicking listeners on the specificed layer.
*
* @constructor
* @param {Element} layer The layer to listen on
*/
function FastClick(layer) {
'use strict';
var oldOnClick, that = this;
/**
* Whether a click is currently being tracked.
*
* @type boolean
*/
this.trackingClick = false,
/**
* The element being tracked for a click.
*
* @type Element
*/
this.targetElement = null;
/**
* The FastClick layer.
*
* @type Element
*/
this.layer = layer;
if (!layer || !layer.nodeType) {
throw new TypeError('Layer must be a document node');
}
// Bind handlers to this instance
this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); };
this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); };
this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); };
this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); };
this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); };
// Devices that don't support touch don't need FastClick
if (typeof window.ontouchstart === 'undefined') {
return;
}
// Set up event handlers as required
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, true);
layer.addEventListener('touchmove', this.onTouchMove, true);
layer.addEventListener('touchend', this.onTouchEnd, true);
layer.addEventListener('touchcancel', this.onTouchCancel, true);
// If a handler is already declared in the element's onclick attribute, it will be fired before
// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
// adding it as listener.
if (typeof layer.onclick === 'function') {
// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
// - the old one won't work if passed to addEventListener directly.
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
/**
* Android requires an exception for labels.
*
* @type boolean
*/
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
/**
* Determine whether a given element requires a native click.
*
* @param {Element} target DOM element
* @returns {boolean} Returns true if the element needs a native click
*/
FastClick.prototype.needsClick = function(target) {
'use strict';
switch (target.nodeName.toLowerCase()) {
case 'label':
case 'video':
return true;
default:
return (/\bneedsclick\b/).test(target.className);
}
};
/**
* Determine whether a given element requires a call to focus to simulate click into element.
*
* @param {Element} target target DOM element.
* @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
*/
FastClick.prototype.needsFocus = function(target) {
'use strict';
switch(target.nodeName.toLowerCase()) {
case 'textarea':
case 'select':
return true;
case 'input':
switch (target.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
default:
return true;
}
break;
default:
return (/\bneedsfocus\b/).test(target.className);
}
};
/**
* Send a click event to the element if it needs it.
*
* @returns {boolean} Whether the click was sent or not
*/
FastClick.prototype.maybeSendClick = function(targetElement, event) {
'use strict';
var clickEvent, touch;
// Prevent the actual click from going though - unless the target node is marked as requiring
// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted
// to open the options list and so the original event is required.
if (this.needsClick(targetElement)) {
return false;
}
// On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
if (document.activeElement && document.activeElement !== targetElement) {
document.activeElement.blur();
}
touch = event.changedTouches[0];
// Synthesise a click event, with an extra attribute so it can be tracked
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
return true;
};
/**
* On touch start, record the position and scroll offset.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchStart = function(event) {
'use strict';
var touch = event.targetTouches[0];
this.trackingClick = true;
this.targetElement = event.target;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
return true;
};
/**
* Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.touchHasMoved = function(event) {
'use strict';
var touch = event.targetTouches[0];
if (Math.abs(touch.pageX - this.touchStartX) > 10
|| Math.abs(touch.pageY - this.touchStartY) > 10) {
return true;
}
return false;
};
/**
* Update the last position.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchMove = function(event) {
'use strict';
if (!this.trackingClick) {
return true;
}
// If the touch has moved, cancel the click tracking
if (this.targetElement !== event.target || this.touchHasMoved(event)) {
this.trackingClick = false;
this.targetElement = null;
}
return true;
};
/**
* On touch end, determine whether to send a click event at once.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchEnd = function(event) {
'use strict';
var forElement, targetElement = this.targetElement;
if (!this.trackingClick) {
return true;
}
this.trackingClick = false;
if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) {
forElement = document.getElementById(targetElement.htmlFor);
if (forElement) {
targetElement.focus();
if (this.deviceIsAndroid) {
return false;
}
if (this.maybeSendClick(forElement, event)) {
event.preventDefault();
}
return false;
}
} else if (this.needsFocus(targetElement)) {
targetElement.focus();
if (targetElement.tagName.toLowerCase() !== 'select') {
event.preventDefault();
}
return false;
}
if (!this.maybeSendClick(targetElement, event)) {
return false;
}
event.preventDefault();
return false;
};
/**
* On touch cancel, stop tracking the click.
*
* @returns {void}
*/
FastClick.prototype.onTouchCancel = function() {
'use strict';
this.trackingClick = false;
this.targetElement = null;
};
/**
* On actual clicks, determine whether this is a touch-generated click, a click action occurring
* naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
* an actual click which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onClick = function(event) {
'use strict';
var oldTargetElement;
if (event.forwardedTouchEvent) {
return true;
}
// If a target element was never set (because a touch event was never fired) allow the click
if (!this.targetElement) {
return true;
}
oldTargetElement = this.targetElement;
this.targetElement = null;
// Programmatically generated events targeting a specific element should be permitted
if (!event.cancelable) {
return true;
}
// Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
// Derive and check the target element to see whether the click needs to be permitted;
// unless explicitly enabled, prevent non-touch click events from triggering actions,
// to prevent ghost/doubleclicks.
if (!this.needsClick(oldTargetElement)) {
// Prevent any user-added listeners declared on FastClick element from being fired.
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
}
// Cancel the event
event.stopPropagation();
event.preventDefault();
return false;
}
// If clicks are permitted, return true for the action to go through.
return true;
};
/**
* Remove all FastClick's event listeners.
*
* @returns {void}
*/
FastClick.prototype.destroy = function() {
'use strict';
var layer = this.layer;
layer.removeEventListener('click', this.onClick, true);
layer.removeEventListener('touchstart', this.onTouchStart, true);
layer.removeEventListener('touchmove', this.onTouchMove, true);
layer.removeEventListener('touchend', this.onTouchEnd, true);
layer.removeEventListener('touchcancel', this.onTouchCancel, true);
};
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(function() {
'use strict';
return FastClick;
});
}
+1
View File
@@ -0,0 +1 @@
function FastClick(t){"use strict";var e,n=this;if(this.trackingClick=!1,this.targetElement=null,this.layer=t,!t||!t.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){FastClick.prototype.onClick.apply(n,arguments)},this.onTouchStart=function(){FastClick.prototype.onTouchStart.apply(n,arguments)},this.onTouchMove=function(){FastClick.prototype.onTouchMove.apply(n,arguments)},this.onTouchEnd=function(){FastClick.prototype.onTouchEnd.apply(n,arguments)},this.onTouchCancel=function(){FastClick.prototype.onTouchCancel.apply(n,arguments)},"undefined"!=typeof window.ontouchstart&&(t.addEventListener("click",this.onClick,!0),t.addEventListener("touchstart",this.onTouchStart,!0),t.addEventListener("touchmove",this.onTouchMove,!0),t.addEventListener("touchend",this.onTouchEnd,!0),t.addEventListener("touchcancel",this.onTouchCancel,!0),"function"==typeof t.onclick&&(e=t.onclick,t.addEventListener("click",function(t){e(t)},!1),t.onclick=null))}FastClick.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,FastClick.prototype.needsClick=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"label":case"video":return!0;default:return/\bneedsclick\b/.test(t.className)}},FastClick.prototype.needsFocus=function(t){"use strict";switch(t.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(t.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1;default:return!0}break;default:return/\bneedsfocus\b/.test(t.className)}},FastClick.prototype.maybeSendClick=function(t,e){"use strict";var n,o;return this.needsClick(t)?!1:(document.activeElement&&document.activeElement!==t&&document.activeElement.blur(),o=e.changedTouches[0],n=document.createEvent("MouseEvents"),n.initMouseEvent("click",!0,!0,window,1,o.screenX,o.screenY,o.clientX,o.clientY,!1,!1,!1,!1,0,null),n.forwardedTouchEvent=!0,t.dispatchEvent(n),!0)},FastClick.prototype.onTouchStart=function(t){"use strict";var e=t.targetTouches[0];return this.trackingClick=!0,this.targetElement=t.target,this.touchStartX=e.pageX,this.touchStartY=e.pageY,!0},FastClick.prototype.touchHasMoved=function(t){"use strict";var e=t.targetTouches[0];return Math.abs(e.pageX-this.touchStartX)>10||Math.abs(e.pageY-this.touchStartY)>10?!0:!1},FastClick.prototype.onTouchMove=function(t){"use strict";return this.trackingClick?((this.targetElement!==t.target||this.touchHasMoved(t))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.onTouchEnd=function(t){"use strict";var e,n=this.targetElement;if(!this.trackingClick)return!0;if(this.trackingClick=!1,"label"===n.nodeName.toLowerCase()&&n.htmlFor){if(e=document.getElementById(n.htmlFor))return n.focus(),this.deviceIsAndroid?!1:(this.maybeSendClick(e,t)&&t.preventDefault(),!1)}else if(this.needsFocus(n))return n.focus(),"select"!==n.tagName.toLowerCase()&&t.preventDefault(),!1;return this.maybeSendClick(n,t)?(t.preventDefault(),!1):!1},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onClick=function(t){"use strict";var e;return t.forwardedTouchEvent?!0:this.targetElement?(e=this.targetElement,this.targetElement=null,t.cancelable?"submit"===t.target.type&&0===t.detail?!0:this.needsClick(e)?!0:(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault(),!1):!0):!0},FastClick.prototype.destroy=function(){"use strict";var t=this.layer;t.removeEventListener("click",this.onClick,!0),t.removeEventListener("touchstart",this.onTouchStart,!0),t.removeEventListener("touchmove",this.onTouchMove,!0),t.removeEventListener("touchend",this.onTouchEnd,!0),t.removeEventListener("touchcancel",this.onTouchCancel,!0)},"function"==typeof define&&define.amd&&define(function(){"use strict";return FastClick});