├── .gitignore ├── .jshintrc ├── .npmignore ├── Gruntfile.js ├── README.md ├── bower.json ├── dist ├── swiftclick.js └── swiftclick.min.js ├── example ├── css │ └── app.css ├── index.html └── js │ ├── app │ └── app.js │ └── libs │ └── swiftclick.js ├── license.txt ├── package.json └── src └── swiftclick.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | 4 | .DS_Store 5 | .DS_Store? 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | Icon? 10 | ehthumbs.db 11 | Thumbs.db -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Based on the JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : false, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "indent" : 4, // {int} Number of spaces to use for indentation 16 | "latedef" : false, // true: Require variables/functions to be defined before being used 17 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 19 | "noempty" : true, // true: Prohibit use of empty blocks 20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 22 | "plusplus" : false, // true: Prohibit use of `++` & `--` 23 | 24 | "quotmark" : true, // Quotation mark consistency: 25 | // false : do nothing (default) 26 | // true : ensure whatever is used is consistent 27 | // "single" : require single quotes 28 | // "double" : require double quotes 29 | 30 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 31 | "unused" : "strict", // true: Require all defined variables be used 32 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 33 | "maxparams" : false, // {int} Max number of formal params allowed per function 34 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 35 | "maxstatements" : false, // {int} Max number statements per function 36 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 37 | "maxlen" : false, // {int} Max number of characters per line 38 | 39 | // Relaxing 40 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 41 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 42 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 43 | "eqnull" : false, // true: Tolerate use of `== null` 44 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 45 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 46 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 47 | // (ex: `for each`, multiple try/catch, function expression…) 48 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 49 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 50 | "funcscope" : false, // true: Tolerate defining variables inside control statements 51 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 52 | "iterator" : false, // true: Tolerate using the `__iterator__` property 53 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 54 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 55 | "laxcomma" : false, // true: Tolerate comma-first style coding 56 | "loopfunc" : false, // true: Tolerate functions being defined in loops 57 | "multistr" : false, // true: Tolerate multi-line strings 58 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 59 | "notypeof" : false, // true: Tolerate invalid typeof operator values 60 | "proto" : false, // true: Tolerate using the `__proto__` property 61 | "scripturl" : false, // true: Tolerate script-targeted URLs 62 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 63 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 64 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 65 | "validthis" : false, // true: Tolerate using this in a non-constructor function 66 | 67 | // Environments 68 | "browser" : true, // Web Browser (window, document, etc) 69 | "browserify" : false, // Browserify (node.js code in the browser) 70 | "couch" : false, // CouchDB 71 | "devel" : true, // Development/debugging (alert, confirm, etc) 72 | "dojo" : false, // Dojo Toolkit 73 | "jasmine" : false, // Jasmine 74 | "jquery" : false, // jQuery 75 | "mocha" : true, // Mocha 76 | "mootools" : false, // MooTools 77 | "node" : true, // Node.js 78 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 79 | "prototypejs" : false, // Prototype and Scriptaculous 80 | "qunit" : false, // QUnit 81 | "rhino" : false, // Rhino 82 | "shelljs" : false, // ShellJS 83 | "worker" : false, // Web Workers 84 | "wsh" : false, // Windows Scripting Host 85 | "yui" : false, // Yahoo User Interface 86 | 87 | // Custom Globals 88 | "globals" : { 89 | 90 | "define" : true // AMD support 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .jshintrc 3 | example/ 4 | src/ 5 | bower.json 6 | Gruntfile.js -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | 'use strict'; 4 | 5 | var jsSrcDir = 'src/'; 6 | var jsSrcFile = 'swiftclick.js'; 7 | var jsDistDir = 'dist/'; 8 | var jsDistFile = 'swiftclick.min.js'; 9 | var jsExampleLibsDir = 'example/js/libs/'; 10 | 11 | grunt.initConfig({ 12 | 13 | watch: { 14 | js: { 15 | files: [jsSrcDir + jsSrcFile], 16 | tasks: ['dist'] 17 | } 18 | }, 19 | 20 | uglify: { 21 | options: { 22 | compress: { 23 | drop_console: true 24 | }, 25 | mangle: true, 26 | beautify: false, 27 | preserveComments: false 28 | }, 29 | dist: { 30 | src: jsSrcDir + jsSrcFile, 31 | dest: jsDistDir + jsDistFile 32 | } 33 | }, 34 | 35 | copy : { 36 | dist : { 37 | src : jsSrcDir + jsSrcFile, 38 | dest : jsDistDir + jsSrcFile // copy with unminified file name 39 | }, 40 | example : { 41 | src : jsSrcDir + jsSrcFile, 42 | dest : jsExampleLibsDir + jsSrcFile // copy with unminified file name 43 | } 44 | } 45 | }); 46 | 47 | require('load-grunt-tasks')(grunt, {pattern: ['grunt-*']}); 48 | 49 | grunt.registerTask('default', ['uglify:dist', 'copy', 'watch']); 50 | grunt.registerTask('dist', ['uglify:dist', 'copy']); 51 | }; 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftClick 2 | 3 | SwiftClick is a library created to eliminate the 300ms click event delay on touch devices that support orientation change and is designed to be super lightweight. 4 | 5 | 6 | ### Teeny-tiny 7 | ~1KB minified & gzipped :-) 8 | 9 | 10 | ## Usage 11 | 12 | You can install SwiftClick using npm: 13 | 14 | ```sh 15 | npm i swiftclick --save 16 | ``` 17 | 18 | You can install via Bower instead, if that's your thing: 19 | 20 | ```sh 21 | bower install swiftclick 22 | ``` 23 | 24 | You can also use CDNJS: https://cdnjs.com/libraries/swiftclick 25 | 26 | Otherwise, you can grab either the [minified](https://raw.githubusercontent.com/munkychop/swiftclick/master/dist/swiftclick.min.js), or [non-minified](https://raw.githubusercontent.com/munkychop/swiftclick/master/dist/swiftclick.js) source from Github. 27 | 28 | ### Include SwiftClick in your application 29 | 30 | If using CommonJS then simply require SwiftClick as per usual: 31 | 32 | ```javascript 33 | var SwiftClick = require('swiftclick'); 34 | ``` 35 | 36 | Otherwise, use a script tag: 37 | 38 | ```html 39 | 40 | ``` 41 | 42 | ### Setup SwiftClick 43 | 44 | Setting up SwiftClick is a very easy process, which mirrors that of FastClick in that instances must be attached to a context element. Touch events from all elements within the context element are automatically captured and converted to click events when necessary, minus the delay. 45 | 46 | Start by creating a reference to a new instance of SwiftClick using the 'attach' helper method and attach it to a context element. Attaching to document.body is easiest if you only need a single instance of SwiftClick: 47 | 48 | ```js 49 | var swiftclick = SwiftClick.attach(document.body); 50 | ``` 51 | 52 | If necessary, multiple instances of SwiftClick can be created for specific context elements which, although not really necessary in most cases, can sometimes be useful for optimising applications with a large amount of HTML: 53 | 54 | ```js 55 | var navigationSwiftClick = SwiftClick.attach(someNavElement); 56 | var uiSwiftClick = SwiftClick.attach(someOtherElement); 57 | ``` 58 | 59 | 60 | ### Default Elements 61 | Once attached, by default SwiftClick will track events originating from the following element types: 62 | 63 | - `` 64 | - `
` 65 | - `` 66 | - ` 131 | 132 |
133 | ``` 134 | 135 | In this example, the first button will not get swift clicks, but the second button will. 136 | 137 | 138 | ### Automatically disabled when not needed 139 | SwiftClick only intercepts events for touch devices that support orientation change, otherwise it just sits there looking pretty. 140 | 141 | ## About the Project 142 | SwiftClick was developed and is currently maintained by [Ivan Hayes](https://twitter.com/munkychop). 143 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swiftclick", 3 | "homepage": "https://github.com/munkychop/swiftclick", 4 | "authors": [ 5 | "Ivan Hayes <@munkychop>" 6 | ], 7 | "description": "SwiftClick is a library created to eliminate the 300ms click event delay on touch devices that support orientation change.", 8 | "main": ["js/dist/swiftclick.min.js", "js/libs/swiftclick.js"], 9 | "keywords": [ 10 | "swiftclick", 11 | "javascript", 12 | "library", 13 | "mobile", 14 | "fastclick", 15 | "tap" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/munkychop/swiftclick.git" 20 | }, 21 | "private": false, 22 | "license": "MIT", 23 | "ignore": [ 24 | "**/.*", 25 | "bower_components", 26 | "css", 27 | "Gruntfile.js", 28 | "index.html", 29 | "js/app", 30 | "js/dist", 31 | "node_modules", 32 | "package.json" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /dist/swiftclick.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @license MIT License (see license.txt) 3 | */ 4 | 5 | 'use strict'; 6 | 7 | function SwiftClick (contextEl) 8 | { 9 | // if SwiftClick has already been initialised on this element then return the instance that's already in the Dictionary. 10 | if (typeof SwiftClick.swiftDictionary[contextEl] !== 'undefined') return SwiftClick.swiftDictionary[contextEl]; 11 | 12 | // add this instance of SwiftClick to the dictionary using the contextEl as the key. 13 | SwiftClick.swiftDictionary[contextEl] = this; 14 | 15 | this.options = 16 | { 17 | elements: {a:'a', div:'div', span:'span', button:'button'}, 18 | minTouchDrift: 4, 19 | maxTouchDrift: 16, 20 | useCssParser: false 21 | }; 22 | 23 | var _self = this; 24 | var _swiftContextEl = contextEl; 25 | var _swiftContextElOriginalClick = _swiftContextEl.onclick; 26 | var _currentlyTrackingTouch = false; 27 | var _touchStartPoint = {x:0, y:0}; 28 | var _scrollStartPoint = {x:0, y:0}; 29 | var _clickedAlready = false; 30 | 31 | 32 | // SwiftClick is only initialised if both touch and orientationchange are supported. 33 | if ('onorientationchange' in window && 'ontouchstart' in window) 34 | { 35 | init(); 36 | } 37 | 38 | function init () 39 | { 40 | // check if the swift el already has a click handler and if so hijack it so it get's fired after SwiftClick's, instead of beforehand. 41 | if (typeof _swiftContextElOriginalClick === 'function') 42 | { 43 | _swiftContextEl.addEventListener('click', hijackedSwiftElClickHandler, false); 44 | _swiftContextEl.onclick = null; 45 | } 46 | 47 | _swiftContextEl.addEventListener('touchstart', touchStartHandler, false); 48 | _swiftContextEl.addEventListener('click', clickHandler, true); 49 | } 50 | 51 | function hijackedSwiftElClickHandler (event) 52 | { 53 | _swiftContextElOriginalClick(event); 54 | } 55 | 56 | function touchStartHandler (event) 57 | { 58 | var targetEl = event.target; 59 | var nodeName = targetEl.nodeName.toLowerCase(); 60 | var touch = event.changedTouches[0]; 61 | 62 | // don't synthesize an event if the node is not an acceptable type (the type isn't in the dictionary). 63 | if (typeof _self.options.elements[nodeName] === 'undefined') 64 | { 65 | return true; 66 | } 67 | 68 | // don't synthesize an event if we are already tracking an element. 69 | if (_currentlyTrackingTouch) 70 | { 71 | return true; 72 | } 73 | 74 | // check parents for 'swiftclick-ignore' class name. 75 | if (_self.options.useCssParser && checkIfElementShouldBeIgnored(targetEl)) 76 | { 77 | _clickedAlready = false; 78 | return true; 79 | } 80 | 81 | event.stopPropagation(); 82 | 83 | _currentlyTrackingTouch = true; 84 | 85 | // store touchstart positions so we can check for changes later (within touchend handler). 86 | _touchStartPoint.x = touch.pageX; 87 | _touchStartPoint.y = touch.pageY; 88 | _scrollStartPoint = getScrollPoint(); 89 | 90 | // only add the 'touchend' listener now that we know the element should be tracked. 91 | targetEl.removeEventListener('touchend', touchEndHandler, false); 92 | targetEl.addEventListener('touchend', touchEndHandler, false); 93 | 94 | targetEl.removeEventListener('touchcancel', touchCancelHandler, false); 95 | targetEl.addEventListener('touchcancel', touchCancelHandler, false); 96 | } 97 | 98 | function touchEndHandler (event) 99 | { 100 | var targetEl = event.target; 101 | var touchend = event.changedTouches[0]; 102 | 103 | targetEl.removeEventListener('touchend', touchEndHandler, false); 104 | 105 | _currentlyTrackingTouch = false; 106 | 107 | // don't synthesize a click event if the touchpoint position has drifted significantly, as the user is not trying to click. 108 | if (hasTouchDriftedTooFar(touchend)) 109 | { 110 | return true; 111 | } 112 | 113 | // prevent default actions and create a synthetic click event before returning false. 114 | event.stopPropagation(); 115 | event.preventDefault(); 116 | 117 | _clickedAlready = false; 118 | 119 | targetEl.focus(); 120 | synthesizeClickEvent(targetEl, touchend); 121 | 122 | // return false in order to surpress the regular click event. 123 | return false; 124 | } 125 | 126 | function touchCancelHandler(event) { 127 | event.target.removeEventListener('touchcancel', touchCancelHandler, false); 128 | 129 | _currentlyTrackingTouch = false; 130 | } 131 | 132 | function clickHandler (event) 133 | { 134 | var targetEl = event.target; 135 | var nodeName = targetEl.nodeName.toLowerCase(); 136 | 137 | if (typeof _self.options.elements[nodeName] !== 'undefined') 138 | { 139 | if (_clickedAlready) 140 | { 141 | _clickedAlready = false; 142 | 143 | event.stopPropagation(); 144 | event.preventDefault(); 145 | return false; 146 | } 147 | 148 | _clickedAlready = true; 149 | } 150 | } 151 | 152 | function synthesizeClickEvent (el, touchend) 153 | { 154 | var clickEvent = document.createEvent('MouseEvents'); 155 | clickEvent.initMouseEvent('click', true, true, window, 1, touchend.screenX, touchend.screenY, touchend.clientX, touchend.clientY, false, false, false, false, 0, null); 156 | 157 | el.dispatchEvent(clickEvent); 158 | } 159 | 160 | function getScrollPoint () 161 | { 162 | var scrollPoint = 163 | { 164 | x : window.pageXOffset || 165 | document.body.scrollLeft || 166 | document.documentElement.scrollLeft || 167 | 0, 168 | y : window.pageYOffset || 169 | document.body.scrollTop || 170 | document.documentElement.scrollTop || 171 | 0 172 | }; 173 | 174 | return scrollPoint; 175 | } 176 | 177 | function checkIfElementShouldBeIgnored (el) 178 | { 179 | var classToIgnore = 'swiftclick-ignore'; 180 | var classToForceClick = 'swiftclick-force'; 181 | var parentEl = el.parentNode; 182 | var shouldIgnoreElement = false; 183 | 184 | // ignore the target el and return early if it has the 'swiftclick-ignore' class. 185 | if (hasClass(el, classToIgnore)) 186 | { 187 | return true; 188 | } 189 | 190 | // don't ignore the target el and return early if it has the 'swiftclick-force' class. 191 | if (hasClass(el, classToForceClick)) 192 | { 193 | return shouldIgnoreElement; 194 | } 195 | 196 | // the topmost element has been reached. 197 | if (parentEl === null) 198 | { 199 | return shouldIgnoreElement; 200 | } 201 | 202 | // ignore the target el if one of its parents has the 'swiftclick-ignore' class. 203 | while (parentEl) 204 | { 205 | if (hasClass(parentEl, classToIgnore)) 206 | { 207 | parentEl = null; 208 | shouldIgnoreElement = true; 209 | } 210 | else 211 | { 212 | parentEl = parentEl.parentNode; 213 | } 214 | } 215 | 216 | return shouldIgnoreElement; 217 | } 218 | 219 | function hasTouchDriftedTooFar (touchend) 220 | { 221 | var maxDrift = _self.options.maxTouchDrift; 222 | var scrollPoint = getScrollPoint(); 223 | 224 | return Math.abs(touchend.pageX - _touchStartPoint.x) > maxDrift || 225 | Math.abs(touchend.pageY - _touchStartPoint.y) > maxDrift || 226 | Math.abs(scrollPoint.x - _scrollStartPoint.x) > maxDrift || 227 | Math.abs(scrollPoint.y - _scrollStartPoint.y) > maxDrift; 228 | } 229 | 230 | function hasClass (el, className) { 231 | 232 | var classExists = typeof el.className !== 'undefined' ? (' ' + el.className + ' ').indexOf(' ' + className + ' ') > -1 : false; 233 | 234 | return classExists; 235 | } 236 | } 237 | 238 | SwiftClick.swiftDictionary = {}; 239 | 240 | SwiftClick.prototype.setMaxTouchDrift = function (maxTouchDrift) 241 | { 242 | if (typeof maxTouchDrift !== 'number') 243 | { 244 | throw new TypeError ('expected "maxTouchDrift" to be of type "number"'); 245 | } 246 | 247 | if (maxTouchDrift < this.options.minTouchDrift) 248 | { 249 | maxTouchDrift = this.options.minTouchDrift; 250 | } 251 | 252 | this.options.maxTouchDrift = maxTouchDrift; 253 | }; 254 | 255 | // add an array of node names (strings) for which swift clicks should be synthesized. 256 | SwiftClick.prototype.addNodeNamesToTrack = function (nodeNamesArray) 257 | { 258 | var i = 0; 259 | var length = nodeNamesArray.length; 260 | var currentNodeName; 261 | 262 | for (i; i < length; i++) 263 | { 264 | if (typeof nodeNamesArray[i] !== 'string') 265 | { 266 | throw new TypeError ('all values within the "nodeNames" array must be of type "string"'); 267 | } 268 | 269 | currentNodeName = nodeNamesArray[i].toLowerCase(); 270 | this.options.elements[currentNodeName] = currentNodeName; 271 | } 272 | }; 273 | 274 | SwiftClick.prototype.replaceNodeNamesToTrack = function (nodeNamesArray) 275 | { 276 | this.options.elements = {}; 277 | this.addNodeNamesToTrack(nodeNamesArray); 278 | }; 279 | 280 | SwiftClick.prototype.useCssParser = function (useParser) 281 | { 282 | this.options.useCssParser = useParser; 283 | }; 284 | 285 | // use a basic implementation of the composition pattern in order to create new instances of SwiftClick. 286 | SwiftClick.attach = function (contextEl) 287 | { 288 | // if SwiftClick has already been initialised on this element then return the instance that's already in the Dictionary. 289 | if (typeof SwiftClick.swiftDictionary[contextEl] !== 'undefined') 290 | { 291 | return SwiftClick.swiftDictionary[contextEl]; 292 | } 293 | 294 | return new SwiftClick(contextEl); 295 | }; 296 | 297 | 298 | // check for AMD/Module support, otherwise define SwiftClick as a global variable. 299 | if (typeof define !== 'undefined' && define.amd) 300 | { 301 | // AMD. Register as an anonymous module. 302 | define (function() 303 | { 304 | return SwiftClick; 305 | }); 306 | 307 | } 308 | else if (typeof module !== 'undefined' && module.exports) 309 | { 310 | module.exports = SwiftClick; 311 | } 312 | else 313 | { 314 | window.SwiftClick = SwiftClick; 315 | } -------------------------------------------------------------------------------- /dist/swiftclick.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function SwiftClick(a){function b(a){n(a)}function c(a){var b=a.target,c=b.nodeName.toLowerCase(),f=a.changedTouches[0];return void 0===l.options.elements[c]||(!!o||(l.options.useCssParser&&i(b)?(r=!1,!0):(a.stopPropagation(),o=!0,p.x=f.pageX,p.y=f.pageY,q=h(),b.removeEventListener("touchend",d,!1),b.addEventListener("touchend",d,!1),b.removeEventListener("touchcancel",e,!1),void b.addEventListener("touchcancel",e,!1))))}function d(a){var b=a.target,c=a.changedTouches[0];return b.removeEventListener("touchend",d,!1),o=!1,!!j(c)||(a.stopPropagation(),a.preventDefault(),r=!1,b.focus(),g(b,c),!1)}function e(a){a.target.removeEventListener("touchcancel",e,!1),o=!1}function f(a){var b=a.target,c=b.nodeName.toLowerCase();if(void 0!==l.options.elements[c]){if(r)return r=!1,a.stopPropagation(),a.preventDefault(),!1;r=!0}}function g(a,b){var c=document.createEvent("MouseEvents");c.initMouseEvent("click",!0,!0,window,1,b.screenX,b.screenY,b.clientX,b.clientY,!1,!1,!1,!1,0,null),a.dispatchEvent(c)}function h(){return{x:window.pageXOffset||document.body.scrollLeft||document.documentElement.scrollLeft||0,y:window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop||0}}function i(a){var b=a.parentNode,c=!1;if(k(a,"swiftclick-ignore"))return!0;if(k(a,"swiftclick-force"))return c;if(null===b)return c;for(;b;)k(b,"swiftclick-ignore")?(b=null,c=!0):b=b.parentNode;return c}function j(a){var b=l.options.maxTouchDrift,c=h();return Math.abs(a.pageX-p.x)>b||Math.abs(a.pageY-p.y)>b||Math.abs(c.x-q.x)>b||Math.abs(c.y-q.y)>b}function k(a,b){return void 0!==a.className&&(" "+a.className+" ").indexOf(" "+b+" ")>-1}if(void 0!==SwiftClick.swiftDictionary[a])return SwiftClick.swiftDictionary[a];SwiftClick.swiftDictionary[a]=this,this.options={elements:{a:"a",div:"div",span:"span",button:"button"},minTouchDrift:4,maxTouchDrift:16,useCssParser:!1};var l=this,m=a,n=m.onclick,o=!1,p={x:0,y:0},q={x:0,y:0},r=!1;"onorientationchange"in window&&"ontouchstart"in window&&function(){"function"==typeof n&&(m.addEventListener("click",b,!1),m.onclick=null),m.addEventListener("touchstart",c,!1),m.addEventListener("click",f,!0)}()}SwiftClick.swiftDictionary={},SwiftClick.prototype.setMaxTouchDrift=function(a){if("number"!=typeof a)throw new TypeError('expected "maxTouchDrift" to be of type "number"');a 2 | 3 | 4 | SwiftClick Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
Div
13 | 14 |
15 | Span - ignored 16 | 17 |
18 | 19 | 20 |
Anchor 21 | 22 |
23 |

textarea elements are not in SwiftClick's dictionary by default, so will behave with 300ms delay.

24 | 25 |

workarounds for elements such as textarea should be implemented seperately from SwiftClick if necessary, as some platforms have obscure bugs, but SwiftClick was designed to be lightweight and target basic element types. See the readme for more details.

26 |
27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/js/app/app.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var swiftclick = window.SwiftClick.attach(document.body); 5 | swiftclick.useCssParser(true); 6 | 7 | // add regular click listeners to all elements with a class of 'test-element'. 8 | var testElements = document.getElementsByClassName('test-element'), 9 | i = 0, 10 | length = testElements.length; 11 | 12 | for (i; i < length; i++) { 13 | testElements[i].addEventListener ('click', elementClicked, false); 14 | } 15 | 16 | 17 | // regular click handler which simply toggles a CSS class on clicked elements. 18 | function elementClicked (event) { 19 | event.preventDefault(); 20 | 21 | console.log('App:: [elementClicked]'); 22 | 23 | var currentElement = event.target; 24 | 25 | currentElement.classList.toggle('bg-colour-change'); 26 | } 27 | 28 | })(); -------------------------------------------------------------------------------- /example/js/libs/swiftclick.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @license MIT License (see license.txt) 3 | */ 4 | 5 | 'use strict'; 6 | 7 | function SwiftClick (contextEl) 8 | { 9 | // if SwiftClick has already been initialised on this element then return the instance that's already in the Dictionary. 10 | if (typeof SwiftClick.swiftDictionary[contextEl] !== 'undefined') return SwiftClick.swiftDictionary[contextEl]; 11 | 12 | // add this instance of SwiftClick to the dictionary using the contextEl as the key. 13 | SwiftClick.swiftDictionary[contextEl] = this; 14 | 15 | this.options = 16 | { 17 | elements: {a:'a', div:'div', span:'span', button:'button'}, 18 | minTouchDrift: 4, 19 | maxTouchDrift: 16, 20 | useCssParser: false 21 | }; 22 | 23 | var _self = this; 24 | var _swiftContextEl = contextEl; 25 | var _swiftContextElOriginalClick = _swiftContextEl.onclick; 26 | var _currentlyTrackingTouch = false; 27 | var _touchStartPoint = {x:0, y:0}; 28 | var _scrollStartPoint = {x:0, y:0}; 29 | var _clickedAlready = false; 30 | 31 | 32 | // SwiftClick is only initialised if both touch and orientationchange are supported. 33 | if ('onorientationchange' in window && 'ontouchstart' in window) 34 | { 35 | init(); 36 | } 37 | 38 | function init () 39 | { 40 | // check if the swift el already has a click handler and if so hijack it so it get's fired after SwiftClick's, instead of beforehand. 41 | if (typeof _swiftContextElOriginalClick === 'function') 42 | { 43 | _swiftContextEl.addEventListener('click', hijackedSwiftElClickHandler, false); 44 | _swiftContextEl.onclick = null; 45 | } 46 | 47 | _swiftContextEl.addEventListener('touchstart', touchStartHandler, false); 48 | _swiftContextEl.addEventListener('click', clickHandler, true); 49 | } 50 | 51 | function hijackedSwiftElClickHandler (event) 52 | { 53 | _swiftContextElOriginalClick(event); 54 | } 55 | 56 | function touchStartHandler (event) 57 | { 58 | var targetEl = event.target; 59 | var nodeName = targetEl.nodeName.toLowerCase(); 60 | var touch = event.changedTouches[0]; 61 | 62 | // don't synthesize an event if the node is not an acceptable type (the type isn't in the dictionary). 63 | if (typeof _self.options.elements[nodeName] === 'undefined') 64 | { 65 | return true; 66 | } 67 | 68 | // don't synthesize an event if we are already tracking an element. 69 | if (_currentlyTrackingTouch) 70 | { 71 | return true; 72 | } 73 | 74 | // check parents for 'swiftclick-ignore' class name. 75 | if (_self.options.useCssParser && checkIfElementShouldBeIgnored(targetEl)) 76 | { 77 | _clickedAlready = false; 78 | return true; 79 | } 80 | 81 | event.stopPropagation(); 82 | 83 | _currentlyTrackingTouch = true; 84 | 85 | // store touchstart positions so we can check for changes later (within touchend handler). 86 | _touchStartPoint.x = touch.pageX; 87 | _touchStartPoint.y = touch.pageY; 88 | _scrollStartPoint = getScrollPoint(); 89 | 90 | // only add the 'touchend' listener now that we know the element should be tracked. 91 | targetEl.removeEventListener('touchend', touchEndHandler, false); 92 | targetEl.addEventListener('touchend', touchEndHandler, false); 93 | 94 | targetEl.removeEventListener('touchcancel', touchCancelHandler, false); 95 | targetEl.addEventListener('touchcancel', touchCancelHandler, false); 96 | } 97 | 98 | function touchEndHandler (event) 99 | { 100 | var targetEl = event.target; 101 | var touchend = event.changedTouches[0]; 102 | 103 | targetEl.removeEventListener('touchend', touchEndHandler, false); 104 | 105 | _currentlyTrackingTouch = false; 106 | 107 | // don't synthesize a click event if the touchpoint position has drifted significantly, as the user is not trying to click. 108 | if (hasTouchDriftedTooFar(touchend)) 109 | { 110 | return true; 111 | } 112 | 113 | // prevent default actions and create a synthetic click event before returning false. 114 | event.stopPropagation(); 115 | event.preventDefault(); 116 | 117 | _clickedAlready = false; 118 | 119 | targetEl.focus(); 120 | synthesizeClickEvent(targetEl, touchend); 121 | 122 | // return false in order to surpress the regular click event. 123 | return false; 124 | } 125 | 126 | function touchCancelHandler(event) { 127 | event.target.removeEventListener('touchcancel', touchCancelHandler, false); 128 | 129 | _currentlyTrackingTouch = false; 130 | } 131 | 132 | function clickHandler (event) 133 | { 134 | var targetEl = event.target; 135 | var nodeName = targetEl.nodeName.toLowerCase(); 136 | 137 | if (typeof _self.options.elements[nodeName] !== 'undefined') 138 | { 139 | if (_clickedAlready) 140 | { 141 | _clickedAlready = false; 142 | 143 | event.stopPropagation(); 144 | event.preventDefault(); 145 | return false; 146 | } 147 | 148 | _clickedAlready = true; 149 | } 150 | } 151 | 152 | function synthesizeClickEvent (el, touchend) 153 | { 154 | var clickEvent = document.createEvent('MouseEvents'); 155 | clickEvent.initMouseEvent('click', true, true, window, 1, touchend.screenX, touchend.screenY, touchend.clientX, touchend.clientY, false, false, false, false, 0, null); 156 | 157 | el.dispatchEvent(clickEvent); 158 | } 159 | 160 | function getScrollPoint () 161 | { 162 | var scrollPoint = 163 | { 164 | x : window.pageXOffset || 165 | document.body.scrollLeft || 166 | document.documentElement.scrollLeft || 167 | 0, 168 | y : window.pageYOffset || 169 | document.body.scrollTop || 170 | document.documentElement.scrollTop || 171 | 0 172 | }; 173 | 174 | return scrollPoint; 175 | } 176 | 177 | function checkIfElementShouldBeIgnored (el) 178 | { 179 | var classToIgnore = 'swiftclick-ignore'; 180 | var classToForceClick = 'swiftclick-force'; 181 | var parentEl = el.parentNode; 182 | var shouldIgnoreElement = false; 183 | 184 | // ignore the target el and return early if it has the 'swiftclick-ignore' class. 185 | if (hasClass(el, classToIgnore)) 186 | { 187 | return true; 188 | } 189 | 190 | // don't ignore the target el and return early if it has the 'swiftclick-force' class. 191 | if (hasClass(el, classToForceClick)) 192 | { 193 | return shouldIgnoreElement; 194 | } 195 | 196 | // the topmost element has been reached. 197 | if (parentEl === null) 198 | { 199 | return shouldIgnoreElement; 200 | } 201 | 202 | // ignore the target el if one of its parents has the 'swiftclick-ignore' class. 203 | while (parentEl) 204 | { 205 | if (hasClass(parentEl, classToIgnore)) 206 | { 207 | parentEl = null; 208 | shouldIgnoreElement = true; 209 | } 210 | else 211 | { 212 | parentEl = parentEl.parentNode; 213 | } 214 | } 215 | 216 | return shouldIgnoreElement; 217 | } 218 | 219 | function hasTouchDriftedTooFar (touchend) 220 | { 221 | var maxDrift = _self.options.maxTouchDrift; 222 | var scrollPoint = getScrollPoint(); 223 | 224 | return Math.abs(touchend.pageX - _touchStartPoint.x) > maxDrift || 225 | Math.abs(touchend.pageY - _touchStartPoint.y) > maxDrift || 226 | Math.abs(scrollPoint.x - _scrollStartPoint.x) > maxDrift || 227 | Math.abs(scrollPoint.y - _scrollStartPoint.y) > maxDrift; 228 | } 229 | 230 | function hasClass (el, className) { 231 | 232 | var classExists = typeof el.className !== 'undefined' ? (' ' + el.className + ' ').indexOf(' ' + className + ' ') > -1 : false; 233 | 234 | return classExists; 235 | } 236 | } 237 | 238 | SwiftClick.swiftDictionary = {}; 239 | 240 | SwiftClick.prototype.setMaxTouchDrift = function (maxTouchDrift) 241 | { 242 | if (typeof maxTouchDrift !== 'number') 243 | { 244 | throw new TypeError ('expected "maxTouchDrift" to be of type "number"'); 245 | } 246 | 247 | if (maxTouchDrift < this.options.minTouchDrift) 248 | { 249 | maxTouchDrift = this.options.minTouchDrift; 250 | } 251 | 252 | this.options.maxTouchDrift = maxTouchDrift; 253 | }; 254 | 255 | // add an array of node names (strings) for which swift clicks should be synthesized. 256 | SwiftClick.prototype.addNodeNamesToTrack = function (nodeNamesArray) 257 | { 258 | var i = 0; 259 | var length = nodeNamesArray.length; 260 | var currentNodeName; 261 | 262 | for (i; i < length; i++) 263 | { 264 | if (typeof nodeNamesArray[i] !== 'string') 265 | { 266 | throw new TypeError ('all values within the "nodeNames" array must be of type "string"'); 267 | } 268 | 269 | currentNodeName = nodeNamesArray[i].toLowerCase(); 270 | this.options.elements[currentNodeName] = currentNodeName; 271 | } 272 | }; 273 | 274 | SwiftClick.prototype.replaceNodeNamesToTrack = function (nodeNamesArray) 275 | { 276 | this.options.elements = {}; 277 | this.addNodeNamesToTrack(nodeNamesArray); 278 | }; 279 | 280 | SwiftClick.prototype.useCssParser = function (useParser) 281 | { 282 | this.options.useCssParser = useParser; 283 | }; 284 | 285 | // use a basic implementation of the composition pattern in order to create new instances of SwiftClick. 286 | SwiftClick.attach = function (contextEl) 287 | { 288 | // if SwiftClick has already been initialised on this element then return the instance that's already in the Dictionary. 289 | if (typeof SwiftClick.swiftDictionary[contextEl] !== 'undefined') 290 | { 291 | return SwiftClick.swiftDictionary[contextEl]; 292 | } 293 | 294 | return new SwiftClick(contextEl); 295 | }; 296 | 297 | 298 | // check for AMD/Module support, otherwise define SwiftClick as a global variable. 299 | if (typeof define !== 'undefined' && define.amd) 300 | { 301 | // AMD. Register as an anonymous module. 302 | define (function() 303 | { 304 | return SwiftClick; 305 | }); 306 | 307 | } 308 | else if (typeof module !== 'undefined' && module.exports) 309 | { 310 | module.exports = SwiftClick; 311 | } 312 | else 313 | { 314 | window.SwiftClick = SwiftClick; 315 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------------------- 2 | 3 | The SwiftClick library has been inspired by FastClick: 4 | 5 | https://github.com/ftlabs/fastclick 6 | Copyright (C) 2012 The Financial Times Ltd. 7 | 8 | --------------------------------------------------------------------------------------------- 9 | 10 | Copyright (C) 2013 munkychop. 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swiftclick", 3 | "title": "SwiftClick", 4 | "description": "Eliminates the 300ms click event delay on touch devices that support orientation change", 5 | "version": "2.1.1", 6 | "homepage": "https://github.com/munkychop/swiftclick", 7 | "author": { 8 | "name": "Ivan Hayes", 9 | "url": "http://www.ivanhayes.com" 10 | }, 11 | "main": "dist/swiftclick.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/munkychop/swiftclick.git" 15 | }, 16 | "keywords": [ 17 | "swiftclick", 18 | "fastclick", 19 | "mobile", 20 | "tap", 21 | "touch", 22 | "click", 23 | "delay" 24 | ], 25 | "private": false, 26 | "license": "MIT", 27 | "devDependencies": { 28 | "grunt": "^1.0.1", 29 | "grunt-contrib-copy": "^1.0.0", 30 | "grunt-contrib-uglify": "^2.0.0", 31 | "grunt-contrib-watch": "^1.0.0", 32 | "load-grunt-tasks": "^3.5.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/swiftclick.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @license MIT License (see license.txt) 3 | */ 4 | 5 | 'use strict'; 6 | 7 | function SwiftClick (contextEl) 8 | { 9 | // if SwiftClick has already been initialised on this element then return the instance that's already in the Dictionary. 10 | if (typeof SwiftClick.swiftDictionary[contextEl] !== 'undefined') return SwiftClick.swiftDictionary[contextEl]; 11 | 12 | // add this instance of SwiftClick to the dictionary using the contextEl as the key. 13 | SwiftClick.swiftDictionary[contextEl] = this; 14 | 15 | this.options = 16 | { 17 | elements: {a:'a', div:'div', span:'span', button:'button'}, 18 | minTouchDrift: 4, 19 | maxTouchDrift: 16, 20 | useCssParser: false 21 | }; 22 | 23 | var _self = this; 24 | var _swiftContextEl = contextEl; 25 | var _swiftContextElOriginalClick = _swiftContextEl.onclick; 26 | var _currentlyTrackingTouch = false; 27 | var _touchStartPoint = {x:0, y:0}; 28 | var _scrollStartPoint = {x:0, y:0}; 29 | var _clickedAlready = false; 30 | 31 | 32 | // SwiftClick is only initialised if both touch and orientationchange are supported. 33 | if ('onorientationchange' in window && 'ontouchstart' in window) 34 | { 35 | init(); 36 | } 37 | 38 | function init () 39 | { 40 | // check if the swift el already has a click handler and if so hijack it so it get's fired after SwiftClick's, instead of beforehand. 41 | if (typeof _swiftContextElOriginalClick === 'function') 42 | { 43 | _swiftContextEl.addEventListener('click', hijackedSwiftElClickHandler, false); 44 | _swiftContextEl.onclick = null; 45 | } 46 | 47 | _swiftContextEl.addEventListener('touchstart', touchStartHandler, false); 48 | _swiftContextEl.addEventListener('click', clickHandler, true); 49 | } 50 | 51 | function hijackedSwiftElClickHandler (event) 52 | { 53 | _swiftContextElOriginalClick(event); 54 | } 55 | 56 | function touchStartHandler (event) 57 | { 58 | var targetEl = event.target; 59 | var nodeName = targetEl.nodeName.toLowerCase(); 60 | var touch = event.changedTouches[0]; 61 | 62 | // don't synthesize an event if the node is not an acceptable type (the type isn't in the dictionary). 63 | if (typeof _self.options.elements[nodeName] === 'undefined') 64 | { 65 | return true; 66 | } 67 | 68 | // don't synthesize an event if we are already tracking an element. 69 | if (_currentlyTrackingTouch) 70 | { 71 | return true; 72 | } 73 | 74 | // check parents for 'swiftclick-ignore' class name. 75 | if (_self.options.useCssParser && checkIfElementShouldBeIgnored(targetEl)) 76 | { 77 | _clickedAlready = false; 78 | return true; 79 | } 80 | 81 | event.stopPropagation(); 82 | 83 | _currentlyTrackingTouch = true; 84 | 85 | // store touchstart positions so we can check for changes later (within touchend handler). 86 | _touchStartPoint.x = touch.pageX; 87 | _touchStartPoint.y = touch.pageY; 88 | _scrollStartPoint = getScrollPoint(); 89 | 90 | // only add the 'touchend' listener now that we know the element should be tracked. 91 | targetEl.removeEventListener('touchend', touchEndHandler, false); 92 | targetEl.addEventListener('touchend', touchEndHandler, false); 93 | 94 | targetEl.removeEventListener('touchcancel', touchCancelHandler, false); 95 | targetEl.addEventListener('touchcancel', touchCancelHandler, false); 96 | } 97 | 98 | function touchEndHandler (event) 99 | { 100 | var targetEl = event.target; 101 | var touchend = event.changedTouches[0]; 102 | 103 | targetEl.removeEventListener('touchend', touchEndHandler, false); 104 | 105 | _currentlyTrackingTouch = false; 106 | 107 | // don't synthesize a click event if the touchpoint position has drifted significantly, as the user is not trying to click. 108 | if (hasTouchDriftedTooFar(touchend)) 109 | { 110 | return true; 111 | } 112 | 113 | // prevent default actions and create a synthetic click event before returning false. 114 | event.stopPropagation(); 115 | event.preventDefault(); 116 | 117 | _clickedAlready = false; 118 | 119 | targetEl.focus(); 120 | synthesizeClickEvent(targetEl, touchend); 121 | 122 | // return false in order to surpress the regular click event. 123 | return false; 124 | } 125 | 126 | function touchCancelHandler(event) { 127 | event.target.removeEventListener('touchcancel', touchCancelHandler, false); 128 | 129 | _currentlyTrackingTouch = false; 130 | } 131 | 132 | function clickHandler (event) 133 | { 134 | var targetEl = event.target; 135 | var nodeName = targetEl.nodeName.toLowerCase(); 136 | 137 | if (typeof _self.options.elements[nodeName] !== 'undefined') 138 | { 139 | if (_clickedAlready) 140 | { 141 | _clickedAlready = false; 142 | 143 | event.stopPropagation(); 144 | event.preventDefault(); 145 | return false; 146 | } 147 | 148 | _clickedAlready = true; 149 | } 150 | } 151 | 152 | function synthesizeClickEvent (el, touchend) 153 | { 154 | var clickEvent = document.createEvent('MouseEvents'); 155 | clickEvent.initMouseEvent('click', true, true, window, 1, touchend.screenX, touchend.screenY, touchend.clientX, touchend.clientY, false, false, false, false, 0, null); 156 | 157 | el.dispatchEvent(clickEvent); 158 | } 159 | 160 | function getScrollPoint () 161 | { 162 | var scrollPoint = 163 | { 164 | x : window.pageXOffset || 165 | document.body.scrollLeft || 166 | document.documentElement.scrollLeft || 167 | 0, 168 | y : window.pageYOffset || 169 | document.body.scrollTop || 170 | document.documentElement.scrollTop || 171 | 0 172 | }; 173 | 174 | return scrollPoint; 175 | } 176 | 177 | function checkIfElementShouldBeIgnored (el) 178 | { 179 | var classToIgnore = 'swiftclick-ignore'; 180 | var classToForceClick = 'swiftclick-force'; 181 | var parentEl = el.parentNode; 182 | var shouldIgnoreElement = false; 183 | 184 | // ignore the target el and return early if it has the 'swiftclick-ignore' class. 185 | if (hasClass(el, classToIgnore)) 186 | { 187 | return true; 188 | } 189 | 190 | // don't ignore the target el and return early if it has the 'swiftclick-force' class. 191 | if (hasClass(el, classToForceClick)) 192 | { 193 | return shouldIgnoreElement; 194 | } 195 | 196 | // the topmost element has been reached. 197 | if (parentEl === null) 198 | { 199 | return shouldIgnoreElement; 200 | } 201 | 202 | // ignore the target el if one of its parents has the 'swiftclick-ignore' class. 203 | while (parentEl) 204 | { 205 | if (hasClass(parentEl, classToIgnore)) 206 | { 207 | parentEl = null; 208 | shouldIgnoreElement = true; 209 | } 210 | else 211 | { 212 | parentEl = parentEl.parentNode; 213 | } 214 | } 215 | 216 | return shouldIgnoreElement; 217 | } 218 | 219 | function hasTouchDriftedTooFar (touchend) 220 | { 221 | var maxDrift = _self.options.maxTouchDrift; 222 | var scrollPoint = getScrollPoint(); 223 | 224 | return Math.abs(touchend.pageX - _touchStartPoint.x) > maxDrift || 225 | Math.abs(touchend.pageY - _touchStartPoint.y) > maxDrift || 226 | Math.abs(scrollPoint.x - _scrollStartPoint.x) > maxDrift || 227 | Math.abs(scrollPoint.y - _scrollStartPoint.y) > maxDrift; 228 | } 229 | 230 | function hasClass (el, className) { 231 | 232 | var classExists = typeof el.className !== 'undefined' ? (' ' + el.className + ' ').indexOf(' ' + className + ' ') > -1 : false; 233 | 234 | return classExists; 235 | } 236 | } 237 | 238 | SwiftClick.swiftDictionary = {}; 239 | 240 | SwiftClick.prototype.setMaxTouchDrift = function (maxTouchDrift) 241 | { 242 | if (typeof maxTouchDrift !== 'number') 243 | { 244 | throw new TypeError ('expected "maxTouchDrift" to be of type "number"'); 245 | } 246 | 247 | if (maxTouchDrift < this.options.minTouchDrift) 248 | { 249 | maxTouchDrift = this.options.minTouchDrift; 250 | } 251 | 252 | this.options.maxTouchDrift = maxTouchDrift; 253 | }; 254 | 255 | // add an array of node names (strings) for which swift clicks should be synthesized. 256 | SwiftClick.prototype.addNodeNamesToTrack = function (nodeNamesArray) 257 | { 258 | var i = 0; 259 | var length = nodeNamesArray.length; 260 | var currentNodeName; 261 | 262 | for (i; i < length; i++) 263 | { 264 | if (typeof nodeNamesArray[i] !== 'string') 265 | { 266 | throw new TypeError ('all values within the "nodeNames" array must be of type "string"'); 267 | } 268 | 269 | currentNodeName = nodeNamesArray[i].toLowerCase(); 270 | this.options.elements[currentNodeName] = currentNodeName; 271 | } 272 | }; 273 | 274 | SwiftClick.prototype.replaceNodeNamesToTrack = function (nodeNamesArray) 275 | { 276 | this.options.elements = {}; 277 | this.addNodeNamesToTrack(nodeNamesArray); 278 | }; 279 | 280 | SwiftClick.prototype.useCssParser = function (useParser) 281 | { 282 | this.options.useCssParser = useParser; 283 | }; 284 | 285 | // use a basic implementation of the composition pattern in order to create new instances of SwiftClick. 286 | SwiftClick.attach = function (contextEl) 287 | { 288 | // if SwiftClick has already been initialised on this element then return the instance that's already in the Dictionary. 289 | if (typeof SwiftClick.swiftDictionary[contextEl] !== 'undefined') 290 | { 291 | return SwiftClick.swiftDictionary[contextEl]; 292 | } 293 | 294 | return new SwiftClick(contextEl); 295 | }; 296 | 297 | 298 | // check for AMD/Module support, otherwise define SwiftClick as a global variable. 299 | if (typeof define !== 'undefined' && define.amd) 300 | { 301 | // AMD. Register as an anonymous module. 302 | define (function() 303 | { 304 | return SwiftClick; 305 | }); 306 | 307 | } 308 | else if (typeof module !== 'undefined' && module.exports) 309 | { 310 | module.exports = SwiftClick; 311 | } 312 | else 313 | { 314 | window.SwiftClick = SwiftClick; 315 | } --------------------------------------------------------------------------------