PageRenderTime 61ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/ajax/libs/fastclick/0.3.5/fastclick.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 376 lines | 188 code | 70 blank | 118 comment | 33 complexity | a9e2db328c2eecebf537f469371c5ef1 MD5 | raw file
  1. /**
  2. * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
  3. *
  4. * @version 0.3.5
  5. * @copyright The Financial Times Limited [All Rights Reserved]
  6. * @license MIT License (see LICENSE.txt)
  7. */
  8. /*jslint browser:true*/
  9. /*global define*/
  10. /**
  11. * Instantiate fast-clicking listeners on the specificed layer.
  12. *
  13. * @constructor
  14. * @param {Element} layer The layer to listen on
  15. */
  16. function FastClick(layer) {
  17. 'use strict';
  18. var oldOnClick, that = this;
  19. /**
  20. * Whether a click is currently being tracked.
  21. *
  22. * @type boolean
  23. */
  24. this.trackingClick = false,
  25. /**
  26. * The element being tracked for a click.
  27. *
  28. * @type Element
  29. */
  30. this.targetElement = null;
  31. /**
  32. * The FastClick layer.
  33. *
  34. * @type Element
  35. */
  36. this.layer = layer;
  37. if (!layer || !layer.nodeType) {
  38. throw new TypeError('Layer must be a document node');
  39. }
  40. // Bind handlers to this instance
  41. this.onClick = function() { FastClick.prototype.onClick.apply(that, arguments); };
  42. this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(that, arguments); };
  43. this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(that, arguments); };
  44. this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(that, arguments); };
  45. this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(that, arguments); };
  46. // Devices that don't support touch don't need FastClick
  47. if (typeof window.ontouchstart === 'undefined') {
  48. return;
  49. }
  50. // Set up event handlers as required
  51. layer.addEventListener('click', this.onClick, true);
  52. layer.addEventListener('touchstart', this.onTouchStart, true);
  53. layer.addEventListener('touchmove', this.onTouchMove, true);
  54. layer.addEventListener('touchend', this.onTouchEnd, true);
  55. layer.addEventListener('touchcancel', this.onTouchCancel, true);
  56. // If a handler is already declared in the element's onclick attribute, it will be fired before
  57. // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
  58. // adding it as listener.
  59. if (typeof layer.onclick === 'function') {
  60. // Android browser on at least 3.2 requires a new reference to the function in layer.onclick
  61. // - the old one won't work if passed to addEventListener directly.
  62. oldOnClick = layer.onclick;
  63. layer.addEventListener('click', function(event) {
  64. oldOnClick(event);
  65. }, false);
  66. layer.onclick = null;
  67. }
  68. }
  69. /**
  70. * Android requires an exception for labels.
  71. *
  72. * @type boolean
  73. */
  74. FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
  75. /**
  76. * Determine whether a given element requires a native click.
  77. *
  78. * @param {Element} target DOM element
  79. * @returns {boolean} Returns true if the element needs a native click
  80. */
  81. FastClick.prototype.needsClick = function(target) {
  82. 'use strict';
  83. switch (target.nodeName.toLowerCase()) {
  84. case 'label':
  85. case 'video':
  86. return true;
  87. default:
  88. return (/\bneedsclick\b/).test(target.className);
  89. }
  90. };
  91. /**
  92. * Determine whether a given element requires a call to focus to simulate click into element.
  93. *
  94. * @param {Element} target target DOM element.
  95. * @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
  96. */
  97. FastClick.prototype.needsFocus = function(target) {
  98. 'use strict';
  99. switch(target.nodeName.toLowerCase()) {
  100. case 'textarea':
  101. case 'select':
  102. return true;
  103. case 'input':
  104. switch (target.type) {
  105. case 'button':
  106. case 'checkbox':
  107. case 'file':
  108. case 'image':
  109. case 'radio':
  110. case 'submit':
  111. return false;
  112. default:
  113. return true;
  114. }
  115. break;
  116. default:
  117. return (/\bneedsfocus\b/).test(target.className);
  118. }
  119. };
  120. /**
  121. * Send a click event to the element if it needs it.
  122. *
  123. * @returns {boolean} Whether the click was sent or not
  124. */
  125. FastClick.prototype.maybeSendClick = function(targetElement, event) {
  126. 'use strict';
  127. var clickEvent, touch;
  128. // Prevent the actual click from going though - unless the target node is marked as requiring
  129. // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted
  130. // to open the options list and so the original event is required.
  131. if (this.needsClick(targetElement)) {
  132. return false;
  133. }
  134. // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
  135. if (document.activeElement && document.activeElement !== targetElement) {
  136. document.activeElement.blur();
  137. }
  138. touch = event.changedTouches[0];
  139. // Synthesise a click event, with an extra attribute so it can be tracked
  140. clickEvent = document.createEvent('MouseEvents');
  141. clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
  142. clickEvent.forwardedTouchEvent = true;
  143. targetElement.dispatchEvent(clickEvent);
  144. return true;
  145. };
  146. /**
  147. * On touch start, record the position and scroll offset.
  148. *
  149. * @param {Event} event
  150. * @returns {boolean}
  151. */
  152. FastClick.prototype.onTouchStart = function(event) {
  153. 'use strict';
  154. var touch = event.targetTouches[0];
  155. this.trackingClick = true;
  156. this.targetElement = event.target;
  157. this.touchStartX = touch.pageX;
  158. this.touchStartY = touch.pageY;
  159. return true;
  160. };
  161. /**
  162. * Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
  163. *
  164. * @param {Event} event
  165. * @returns {boolean}
  166. */
  167. FastClick.prototype.touchHasMoved = function(event) {
  168. 'use strict';
  169. var touch = event.targetTouches[0];
  170. if (Math.abs(touch.pageX - this.touchStartX) > 10
  171. || Math.abs(touch.pageY - this.touchStartY) > 10) {
  172. return true;
  173. }
  174. return false;
  175. };
  176. /**
  177. * Update the last position.
  178. *
  179. * @param {Event} event
  180. * @returns {boolean}
  181. */
  182. FastClick.prototype.onTouchMove = function(event) {
  183. 'use strict';
  184. if (!this.trackingClick) {
  185. return true;
  186. }
  187. // If the touch has moved, cancel the click tracking
  188. if (this.targetElement !== event.target || this.touchHasMoved(event)) {
  189. this.trackingClick = false;
  190. this.targetElement = null;
  191. }
  192. return true;
  193. };
  194. /**
  195. * On touch end, determine whether to send a click event at once.
  196. *
  197. * @param {Event} event
  198. * @returns {boolean}
  199. */
  200. FastClick.prototype.onTouchEnd = function(event) {
  201. 'use strict';
  202. var forElement, targetElement = this.targetElement;
  203. if (!this.trackingClick) {
  204. return true;
  205. }
  206. this.trackingClick = false;
  207. if (targetElement.nodeName.toLowerCase() === 'label' && targetElement.htmlFor) {
  208. forElement = document.getElementById(targetElement.htmlFor);
  209. if (forElement) {
  210. targetElement.focus();
  211. if (this.deviceIsAndroid) {
  212. return false;
  213. }
  214. if (this.maybeSendClick(forElement, event)) {
  215. event.preventDefault();
  216. }
  217. return false;
  218. }
  219. } else if (this.needsFocus(targetElement)) {
  220. targetElement.focus();
  221. if (targetElement.tagName.toLowerCase() !== 'select') {
  222. event.preventDefault();
  223. }
  224. return false;
  225. }
  226. if (!this.maybeSendClick(targetElement, event)) {
  227. return false;
  228. }
  229. event.preventDefault();
  230. return false;
  231. };
  232. /**
  233. * On touch cancel, stop tracking the click.
  234. *
  235. * @returns {void}
  236. */
  237. FastClick.prototype.onTouchCancel = function() {
  238. 'use strict';
  239. this.trackingClick = false;
  240. this.targetElement = null;
  241. };
  242. /**
  243. * On actual clicks, determine whether this is a touch-generated click, a click action occurring
  244. * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
  245. * an actual click which should be permitted.
  246. *
  247. * @param {Event} event
  248. * @returns {boolean}
  249. */
  250. FastClick.prototype.onClick = function(event) {
  251. 'use strict';
  252. var oldTargetElement;
  253. if (event.forwardedTouchEvent) {
  254. return true;
  255. }
  256. // If a target element was never set (because a touch event was never fired) allow the click
  257. if (!this.targetElement) {
  258. return true;
  259. }
  260. oldTargetElement = this.targetElement;
  261. this.targetElement = null;
  262. // Programmatically generated events targeting a specific element should be permitted
  263. if (!event.cancelable) {
  264. return true;
  265. }
  266. // 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.
  267. if (event.target.type === 'submit' && event.detail === 0) {
  268. return true;
  269. }
  270. // Derive and check the target element to see whether the click needs to be permitted;
  271. // unless explicitly enabled, prevent non-touch click events from triggering actions,
  272. // to prevent ghost/doubleclicks.
  273. if (!this.needsClick(oldTargetElement)) {
  274. // Prevent any user-added listeners declared on FastClick element from being fired.
  275. if (event.stopImmediatePropagation) {
  276. event.stopImmediatePropagation();
  277. }
  278. // Cancel the event
  279. event.stopPropagation();
  280. event.preventDefault();
  281. return false;
  282. }
  283. // If clicks are permitted, return true for the action to go through.
  284. return true;
  285. };
  286. /**
  287. * Remove all FastClick's event listeners.
  288. *
  289. * @returns {void}
  290. */
  291. FastClick.prototype.destroy = function() {
  292. 'use strict';
  293. var layer = this.layer;
  294. layer.removeEventListener('click', this.onClick, true);
  295. layer.removeEventListener('touchstart', this.onTouchStart, true);
  296. layer.removeEventListener('touchmove', this.onTouchMove, true);
  297. layer.removeEventListener('touchend', this.onTouchEnd, true);
  298. layer.removeEventListener('touchcancel', this.onTouchCancel, true);
  299. };
  300. if (typeof define === 'function' && define.amd) {
  301. // AMD. Register as an anonymous module.
  302. define(function() {
  303. 'use strict';
  304. return FastClick;
  305. });
  306. }