hammer.js 72 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643
  1. /*! Hammer.JS - v2.0.7 - 2016-04-22
  2. * http://hammerjs.github.io/
  3. *
  4. * Copyright (c) 2016 Jorik Tangelder;
  5. * Licensed under the MIT license */
  6. (function(window, document, exportName, undefined) {
  7. 'use strict';
  8. var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
  9. var TEST_ELEMENT = document.createElement('div');
  10. var TYPE_FUNCTION = 'function';
  11. var round = Math.round;
  12. var abs = Math.abs;
  13. var now = Date.now;
  14. /**
  15. * set a timeout with a given scope
  16. * @param {Function} fn
  17. * @param {Number} timeout
  18. * @param {Object} context
  19. * @returns {number}
  20. */
  21. function setTimeoutContext(fn, timeout, context) {
  22. return setTimeout(bindFn(fn, context), timeout);
  23. }
  24. /**
  25. * if the argument is an array, we want to execute the fn on each entry
  26. * if it aint an array we don't want to do a thing.
  27. * this is used by all the methods that accept a single and array argument.
  28. * @param {*|Array} arg
  29. * @param {String} fn
  30. * @param {Object} [context]
  31. * @returns {Boolean}
  32. */
  33. function invokeArrayArg(arg, fn, context) {
  34. if (Array.isArray(arg)) {
  35. each(arg, context[fn], context);
  36. return true;
  37. }
  38. return false;
  39. }
  40. /**
  41. * walk objects and arrays
  42. * @param {Object} obj
  43. * @param {Function} iterator
  44. * @param {Object} context
  45. */
  46. function each(obj, iterator, context) {
  47. var i;
  48. if (!obj) {
  49. return;
  50. }
  51. if (obj.forEach) {
  52. obj.forEach(iterator, context);
  53. } else if (obj.length !== undefined) {
  54. i = 0;
  55. while (i < obj.length) {
  56. iterator.call(context, obj[i], i, obj);
  57. i++;
  58. }
  59. } else {
  60. for (i in obj) {
  61. obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
  62. }
  63. }
  64. }
  65. /**
  66. * wrap a method with a deprecation warning and stack trace
  67. * @param {Function} method
  68. * @param {String} name
  69. * @param {String} message
  70. * @returns {Function} A new function wrapping the supplied method.
  71. */
  72. function deprecate(method, name, message) {
  73. var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
  74. return function() {
  75. var e = new Error('get-stack-trace');
  76. var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
  77. .replace(/^\s+at\s+/gm, '')
  78. .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
  79. var log = window.console && (window.console.warn || window.console.log);
  80. if (log) {
  81. log.call(window.console, deprecationMessage, stack);
  82. }
  83. return method.apply(this, arguments);
  84. };
  85. }
  86. /**
  87. * extend object.
  88. * means that properties in dest will be overwritten by the ones in src.
  89. * @param {Object} target
  90. * @param {...Object} objects_to_assign
  91. * @returns {Object} target
  92. */
  93. var assign;
  94. if (typeof Object.assign !== 'function') {
  95. assign = function assign(target) {
  96. if (target === undefined || target === null) {
  97. throw new TypeError('Cannot convert undefined or null to object');
  98. }
  99. var output = Object(target);
  100. for (var index = 1; index < arguments.length; index++) {
  101. var source = arguments[index];
  102. if (source !== undefined && source !== null) {
  103. for (var nextKey in source) {
  104. if (source.hasOwnProperty(nextKey)) {
  105. output[nextKey] = source[nextKey];
  106. }
  107. }
  108. }
  109. }
  110. return output;
  111. };
  112. } else {
  113. assign = Object.assign;
  114. }
  115. /**
  116. * extend object.
  117. * means that properties in dest will be overwritten by the ones in src.
  118. * @param {Object} dest
  119. * @param {Object} src
  120. * @param {Boolean} [merge=false]
  121. * @returns {Object} dest
  122. */
  123. var extend = deprecate(function extend(dest, src, merge) {
  124. var keys = Object.keys(src);
  125. var i = 0;
  126. while (i < keys.length) {
  127. if (!merge || (merge && dest[keys[i]] === undefined)) {
  128. dest[keys[i]] = src[keys[i]];
  129. }
  130. i++;
  131. }
  132. return dest;
  133. }, 'extend', 'Use `assign`.');
  134. /**
  135. * merge the values from src in the dest.
  136. * means that properties that exist in dest will not be overwritten by src
  137. * @param {Object} dest
  138. * @param {Object} src
  139. * @returns {Object} dest
  140. */
  141. var merge = deprecate(function merge(dest, src) {
  142. return extend(dest, src, true);
  143. }, 'merge', 'Use `assign`.');
  144. /**
  145. * simple class inheritance
  146. * @param {Function} child
  147. * @param {Function} base
  148. * @param {Object} [properties]
  149. */
  150. function inherit(child, base, properties) {
  151. var baseP = base.prototype,
  152. childP;
  153. childP = child.prototype = Object.create(baseP);
  154. childP.constructor = child;
  155. childP._super = baseP;
  156. if (properties) {
  157. assign(childP, properties);
  158. }
  159. }
  160. /**
  161. * simple function bind
  162. * @param {Function} fn
  163. * @param {Object} context
  164. * @returns {Function}
  165. */
  166. function bindFn(fn, context) {
  167. return function boundFn() {
  168. return fn.apply(context, arguments);
  169. };
  170. }
  171. /**
  172. * let a boolean value also be a function that must return a boolean
  173. * this first item in args will be used as the context
  174. * @param {Boolean|Function} val
  175. * @param {Array} [args]
  176. * @returns {Boolean}
  177. */
  178. function boolOrFn(val, args) {
  179. if (typeof val == TYPE_FUNCTION) {
  180. return val.apply(args ? args[0] || undefined : undefined, args);
  181. }
  182. return val;
  183. }
  184. /**
  185. * use the val2 when val1 is undefined
  186. * @param {*} val1
  187. * @param {*} val2
  188. * @returns {*}
  189. */
  190. function ifUndefined(val1, val2) {
  191. return (val1 === undefined) ? val2 : val1;
  192. }
  193. /**
  194. * addEventListener with multiple events at once
  195. * @param {EventTarget} target
  196. * @param {String} types
  197. * @param {Function} handler
  198. */
  199. function addEventListeners(target, types, handler) {
  200. each(splitStr(types), function(type) {
  201. target.addEventListener(type, handler, false);
  202. });
  203. }
  204. /**
  205. * removeEventListener with multiple events at once
  206. * @param {EventTarget} target
  207. * @param {String} types
  208. * @param {Function} handler
  209. */
  210. function removeEventListeners(target, types, handler) {
  211. each(splitStr(types), function(type) {
  212. target.removeEventListener(type, handler, false);
  213. });
  214. }
  215. /**
  216. * find if a node is in the given parent
  217. * @method hasParent
  218. * @param {HTMLElement} node
  219. * @param {HTMLElement} parent
  220. * @return {Boolean} found
  221. */
  222. function hasParent(node, parent) {
  223. while (node) {
  224. if (node == parent) {
  225. return true;
  226. }
  227. node = node.parentNode;
  228. }
  229. return false;
  230. }
  231. /**
  232. * small indexOf wrapper
  233. * @param {String} str
  234. * @param {String} find
  235. * @returns {Boolean} found
  236. */
  237. function inStr(str, find) {
  238. return str.indexOf(find) > -1;
  239. }
  240. /**
  241. * split string on whitespace
  242. * @param {String} str
  243. * @returns {Array} words
  244. */
  245. function splitStr(str) {
  246. return str.trim().split(/\s+/g);
  247. }
  248. /**
  249. * find if a array contains the object using indexOf or a simple polyFill
  250. * @param {Array} src
  251. * @param {String} find
  252. * @param {String} [findByKey]
  253. * @return {Boolean|Number} false when not found, or the index
  254. */
  255. function inArray(src, find, findByKey) {
  256. if (src.indexOf && !findByKey) {
  257. return src.indexOf(find);
  258. } else {
  259. var i = 0;
  260. while (i < src.length) {
  261. if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
  262. return i;
  263. }
  264. i++;
  265. }
  266. return -1;
  267. }
  268. }
  269. /**
  270. * convert array-like objects to real arrays
  271. * @param {Object} obj
  272. * @returns {Array}
  273. */
  274. function toArray(obj) {
  275. return Array.prototype.slice.call(obj, 0);
  276. }
  277. /**
  278. * unique array with objects based on a key (like 'id') or just by the array's value
  279. * @param {Array} src [{id:1},{id:2},{id:1}]
  280. * @param {String} [key]
  281. * @param {Boolean} [sort=False]
  282. * @returns {Array} [{id:1},{id:2}]
  283. */
  284. function uniqueArray(src, key, sort) {
  285. var results = [];
  286. var values = [];
  287. var i = 0;
  288. while (i < src.length) {
  289. var val = key ? src[i][key] : src[i];
  290. if (inArray(values, val) < 0) {
  291. results.push(src[i]);
  292. }
  293. values[i] = val;
  294. i++;
  295. }
  296. if (sort) {
  297. if (!key) {
  298. results = results.sort();
  299. } else {
  300. results = results.sort(function sortUniqueArray(a, b) {
  301. return a[key] > b[key];
  302. });
  303. }
  304. }
  305. return results;
  306. }
  307. /**
  308. * get the prefixed property
  309. * @param {Object} obj
  310. * @param {String} property
  311. * @returns {String|Undefined} prefixed
  312. */
  313. function prefixed(obj, property) {
  314. var prefix, prop;
  315. var camelProp = property[0].toUpperCase() + property.slice(1);
  316. var i = 0;
  317. while (i < VENDOR_PREFIXES.length) {
  318. prefix = VENDOR_PREFIXES[i];
  319. prop = (prefix) ? prefix + camelProp : property;
  320. if (prop in obj) {
  321. return prop;
  322. }
  323. i++;
  324. }
  325. return undefined;
  326. }
  327. /**
  328. * get a unique id
  329. * @returns {number} uniqueId
  330. */
  331. var _uniqueId = 1;
  332. function uniqueId() {
  333. return _uniqueId++;
  334. }
  335. /**
  336. * get the window object of an element
  337. * @param {HTMLElement} element
  338. * @returns {DocumentView|Window}
  339. */
  340. function getWindowForElement(element) {
  341. var doc = element.ownerDocument || element;
  342. return (doc.defaultView || doc.parentWindow || window);
  343. }
  344. var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
  345. var SUPPORT_TOUCH = ('ontouchstart' in window);
  346. var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
  347. var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
  348. var INPUT_TYPE_TOUCH = 'touch';
  349. var INPUT_TYPE_PEN = 'pen';
  350. var INPUT_TYPE_MOUSE = 'mouse';
  351. var INPUT_TYPE_KINECT = 'kinect';
  352. var COMPUTE_INTERVAL = 25;
  353. var INPUT_START = 1;
  354. var INPUT_MOVE = 2;
  355. var INPUT_END = 4;
  356. var INPUT_CANCEL = 8;
  357. var DIRECTION_NONE = 1;
  358. var DIRECTION_LEFT = 2;
  359. var DIRECTION_RIGHT = 4;
  360. var DIRECTION_UP = 8;
  361. var DIRECTION_DOWN = 16;
  362. var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
  363. var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
  364. var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
  365. var PROPS_XY = ['x', 'y'];
  366. var PROPS_CLIENT_XY = ['clientX', 'clientY'];
  367. /**
  368. * create new input type manager
  369. * @param {Manager} manager
  370. * @param {Function} callback
  371. * @returns {Input}
  372. * @constructor
  373. */
  374. function Input(manager, callback) {
  375. var self = this;
  376. this.manager = manager;
  377. this.callback = callback;
  378. this.element = manager.element;
  379. this.target = manager.options.inputTarget;
  380. // smaller wrapper around the handler, for the scope and the enabled state of the manager,
  381. // so when disabled the input events are completely bypassed.
  382. this.domHandler = function(ev) {
  383. if (boolOrFn(manager.options.enable, [manager])) {
  384. self.handler(ev);
  385. }
  386. };
  387. this.init();
  388. }
  389. Input.prototype = {
  390. /**
  391. * should handle the inputEvent data and trigger the callback
  392. * @virtual
  393. */
  394. handler: function() { },
  395. /**
  396. * bind the events
  397. */
  398. init: function() {
  399. this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
  400. this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
  401. this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
  402. },
  403. /**
  404. * unbind the events
  405. */
  406. destroy: function() {
  407. this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
  408. this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
  409. this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
  410. }
  411. };
  412. /**
  413. * create new input type manager
  414. * called by the Manager constructor
  415. * @param {Hammer} manager
  416. * @returns {Input}
  417. */
  418. function createInputInstance(manager) {
  419. var Type;
  420. var inputClass = manager.options.inputClass;
  421. if (inputClass) {
  422. Type = inputClass;
  423. } else if (SUPPORT_POINTER_EVENTS) {
  424. Type = PointerEventInput;
  425. } else if (SUPPORT_ONLY_TOUCH) {
  426. Type = TouchInput;
  427. } else if (!SUPPORT_TOUCH) {
  428. Type = MouseInput;
  429. } else {
  430. Type = TouchMouseInput;
  431. }
  432. return new (Type)(manager, inputHandler);
  433. }
  434. /**
  435. * handle input events
  436. * @param {Manager} manager
  437. * @param {String} eventType
  438. * @param {Object} input
  439. */
  440. function inputHandler(manager, eventType, input) {
  441. var pointersLen = input.pointers.length;
  442. var changedPointersLen = input.changedPointers.length;
  443. var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
  444. var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
  445. input.isFirst = !!isFirst;
  446. input.isFinal = !!isFinal;
  447. if (isFirst) {
  448. manager.session = {};
  449. }
  450. // source event is the normalized value of the domEvents
  451. // like 'touchstart, mouseup, pointerdown'
  452. input.eventType = eventType;
  453. // compute scale, rotation etc
  454. computeInputData(manager, input);
  455. // emit secret event
  456. manager.emit('hammer.input', input);
  457. manager.recognize(input);
  458. manager.session.prevInput = input;
  459. }
  460. /**
  461. * extend the data with some usable properties like scale, rotate, velocity etc
  462. * @param {Object} manager
  463. * @param {Object} input
  464. */
  465. function computeInputData(manager, input) {
  466. var session = manager.session;
  467. var pointers = input.pointers;
  468. var pointersLength = pointers.length;
  469. // store the first input to calculate the distance and direction
  470. if (!session.firstInput) {
  471. session.firstInput = simpleCloneInputData(input);
  472. }
  473. // to compute scale and rotation we need to store the multiple touches
  474. if (pointersLength > 1 && !session.firstMultiple) {
  475. session.firstMultiple = simpleCloneInputData(input);
  476. } else if (pointersLength === 1) {
  477. session.firstMultiple = false;
  478. }
  479. var firstInput = session.firstInput;
  480. var firstMultiple = session.firstMultiple;
  481. var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
  482. var center = input.center = getCenter(pointers);
  483. input.timeStamp = now();
  484. input.deltaTime = input.timeStamp - firstInput.timeStamp;
  485. input.angle = getAngle(offsetCenter, center);
  486. input.distance = getDistance(offsetCenter, center);
  487. computeDeltaXY(session, input);
  488. input.offsetDirection = getDirection(input.deltaX, input.deltaY);
  489. var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
  490. input.overallVelocityX = overallVelocity.x;
  491. input.overallVelocityY = overallVelocity.y;
  492. input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
  493. input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
  494. input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
  495. input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
  496. session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
  497. computeIntervalInputData(session, input);
  498. // find the correct target
  499. var target = manager.element;
  500. if (hasParent(input.srcEvent.target, target)) {
  501. target = input.srcEvent.target;
  502. }
  503. input.target = target;
  504. }
  505. function computeDeltaXY(session, input) {
  506. var center = input.center;
  507. var offset = session.offsetDelta || {};
  508. var prevDelta = session.prevDelta || {};
  509. var prevInput = session.prevInput || {};
  510. if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
  511. prevDelta = session.prevDelta = {
  512. x: prevInput.deltaX || 0,
  513. y: prevInput.deltaY || 0
  514. };
  515. offset = session.offsetDelta = {
  516. x: center.x,
  517. y: center.y
  518. };
  519. }
  520. input.deltaX = prevDelta.x + (center.x - offset.x);
  521. input.deltaY = prevDelta.y + (center.y - offset.y);
  522. }
  523. /**
  524. * velocity is calculated every x ms
  525. * @param {Object} session
  526. * @param {Object} input
  527. */
  528. function computeIntervalInputData(session, input) {
  529. var last = session.lastInterval || input,
  530. deltaTime = input.timeStamp - last.timeStamp,
  531. velocity, velocityX, velocityY, direction;
  532. if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
  533. var deltaX = input.deltaX - last.deltaX;
  534. var deltaY = input.deltaY - last.deltaY;
  535. var v = getVelocity(deltaTime, deltaX, deltaY);
  536. velocityX = v.x;
  537. velocityY = v.y;
  538. velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
  539. direction = getDirection(deltaX, deltaY);
  540. session.lastInterval = input;
  541. } else {
  542. // use latest velocity info if it doesn't overtake a minimum period
  543. velocity = last.velocity;
  544. velocityX = last.velocityX;
  545. velocityY = last.velocityY;
  546. direction = last.direction;
  547. }
  548. input.velocity = velocity;
  549. input.velocityX = velocityX;
  550. input.velocityY = velocityY;
  551. input.direction = direction;
  552. }
  553. /**
  554. * create a simple clone from the input used for storage of firstInput and firstMultiple
  555. * @param {Object} input
  556. * @returns {Object} clonedInputData
  557. */
  558. function simpleCloneInputData(input) {
  559. // make a simple copy of the pointers because we will get a reference if we don't
  560. // we only need clientXY for the calculations
  561. var pointers = [];
  562. var i = 0;
  563. while (i < input.pointers.length) {
  564. pointers[i] = {
  565. clientX: round(input.pointers[i].clientX),
  566. clientY: round(input.pointers[i].clientY)
  567. };
  568. i++;
  569. }
  570. return {
  571. timeStamp: now(),
  572. pointers: pointers,
  573. center: getCenter(pointers),
  574. deltaX: input.deltaX,
  575. deltaY: input.deltaY
  576. };
  577. }
  578. /**
  579. * get the center of all the pointers
  580. * @param {Array} pointers
  581. * @return {Object} center contains `x` and `y` properties
  582. */
  583. function getCenter(pointers) {
  584. var pointersLength = pointers.length;
  585. // no need to loop when only one touch
  586. if (pointersLength === 1) {
  587. return {
  588. x: round(pointers[0].clientX),
  589. y: round(pointers[0].clientY)
  590. };
  591. }
  592. var x = 0, y = 0, i = 0;
  593. while (i < pointersLength) {
  594. x += pointers[i].clientX;
  595. y += pointers[i].clientY;
  596. i++;
  597. }
  598. return {
  599. x: round(x / pointersLength),
  600. y: round(y / pointersLength)
  601. };
  602. }
  603. /**
  604. * calculate the velocity between two points. unit is in px per ms.
  605. * @param {Number} deltaTime
  606. * @param {Number} x
  607. * @param {Number} y
  608. * @return {Object} velocity `x` and `y`
  609. */
  610. function getVelocity(deltaTime, x, y) {
  611. return {
  612. x: x / deltaTime || 0,
  613. y: y / deltaTime || 0
  614. };
  615. }
  616. /**
  617. * get the direction between two points
  618. * @param {Number} x
  619. * @param {Number} y
  620. * @return {Number} direction
  621. */
  622. function getDirection(x, y) {
  623. if (x === y) {
  624. return DIRECTION_NONE;
  625. }
  626. if (abs(x) >= abs(y)) {
  627. return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  628. }
  629. return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
  630. }
  631. /**
  632. * calculate the absolute distance between two points
  633. * @param {Object} p1 {x, y}
  634. * @param {Object} p2 {x, y}
  635. * @param {Array} [props] containing x and y keys
  636. * @return {Number} distance
  637. */
  638. function getDistance(p1, p2, props) {
  639. if (!props) {
  640. props = PROPS_XY;
  641. }
  642. var x = p2[props[0]] - p1[props[0]],
  643. y = p2[props[1]] - p1[props[1]];
  644. return Math.sqrt((x * x) + (y * y));
  645. }
  646. /**
  647. * calculate the angle between two coordinates
  648. * @param {Object} p1
  649. * @param {Object} p2
  650. * @param {Array} [props] containing x and y keys
  651. * @return {Number} angle
  652. */
  653. function getAngle(p1, p2, props) {
  654. if (!props) {
  655. props = PROPS_XY;
  656. }
  657. var x = p2[props[0]] - p1[props[0]],
  658. y = p2[props[1]] - p1[props[1]];
  659. return Math.atan2(y, x) * 180 / Math.PI;
  660. }
  661. /**
  662. * calculate the rotation degrees between two pointersets
  663. * @param {Array} start array of pointers
  664. * @param {Array} end array of pointers
  665. * @return {Number} rotation
  666. */
  667. function getRotation(start, end) {
  668. return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
  669. }
  670. /**
  671. * calculate the scale factor between two pointersets
  672. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  673. * @param {Array} start array of pointers
  674. * @param {Array} end array of pointers
  675. * @return {Number} scale
  676. */
  677. function getScale(start, end) {
  678. return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
  679. }
  680. var MOUSE_INPUT_MAP = {
  681. mousedown: INPUT_START,
  682. mousemove: INPUT_MOVE,
  683. mouseup: INPUT_END
  684. };
  685. var MOUSE_ELEMENT_EVENTS = 'mousedown';
  686. var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
  687. /**
  688. * Mouse events input
  689. * @constructor
  690. * @extends Input
  691. */
  692. function MouseInput() {
  693. this.evEl = MOUSE_ELEMENT_EVENTS;
  694. this.evWin = MOUSE_WINDOW_EVENTS;
  695. this.pressed = false; // mousedown state
  696. Input.apply(this, arguments);
  697. }
  698. inherit(MouseInput, Input, {
  699. /**
  700. * handle mouse events
  701. * @param {Object} ev
  702. */
  703. handler: function MEhandler(ev) {
  704. var eventType = MOUSE_INPUT_MAP[ev.type];
  705. // on start we want to have the left mouse button down
  706. if (eventType & INPUT_START && ev.button === 0) {
  707. this.pressed = true;
  708. }
  709. if (eventType & INPUT_MOVE && ev.which !== 1) {
  710. eventType = INPUT_END;
  711. }
  712. // mouse must be down
  713. if (!this.pressed) {
  714. return;
  715. }
  716. if (eventType & INPUT_END) {
  717. this.pressed = false;
  718. }
  719. this.callback(this.manager, eventType, {
  720. pointers: [ev],
  721. changedPointers: [ev],
  722. pointerType: INPUT_TYPE_MOUSE,
  723. srcEvent: ev
  724. });
  725. }
  726. });
  727. var POINTER_INPUT_MAP = {
  728. pointerdown: INPUT_START,
  729. pointermove: INPUT_MOVE,
  730. pointerup: INPUT_END,
  731. pointercancel: INPUT_CANCEL,
  732. pointerout: INPUT_CANCEL
  733. };
  734. // in IE10 the pointer types is defined as an enum
  735. var IE10_POINTER_TYPE_ENUM = {
  736. 2: INPUT_TYPE_TOUCH,
  737. 3: INPUT_TYPE_PEN,
  738. 4: INPUT_TYPE_MOUSE,
  739. 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
  740. };
  741. var POINTER_ELEMENT_EVENTS = 'pointerdown';
  742. var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
  743. // IE10 has prefixed support, and case-sensitive
  744. if (window.MSPointerEvent && !window.PointerEvent) {
  745. POINTER_ELEMENT_EVENTS = 'MSPointerDown';
  746. POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
  747. }
  748. /**
  749. * Pointer events input
  750. * @constructor
  751. * @extends Input
  752. */
  753. function PointerEventInput() {
  754. this.evEl = POINTER_ELEMENT_EVENTS;
  755. this.evWin = POINTER_WINDOW_EVENTS;
  756. Input.apply(this, arguments);
  757. this.store = (this.manager.session.pointerEvents = []);
  758. }
  759. inherit(PointerEventInput, Input, {
  760. /**
  761. * handle mouse events
  762. * @param {Object} ev
  763. */
  764. handler: function PEhandler(ev) {
  765. var store = this.store;
  766. var removePointer = false;
  767. var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
  768. var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
  769. var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
  770. var isTouch = (pointerType == INPUT_TYPE_TOUCH);
  771. // get index of the event in the store
  772. var storeIndex = inArray(store, ev.pointerId, 'pointerId');
  773. // start and mouse must be down
  774. if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
  775. if (storeIndex < 0) {
  776. store.push(ev);
  777. storeIndex = store.length - 1;
  778. }
  779. } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
  780. removePointer = true;
  781. }
  782. // it not found, so the pointer hasn't been down (so it's probably a hover)
  783. if (storeIndex < 0) {
  784. return;
  785. }
  786. // update the event in the store
  787. store[storeIndex] = ev;
  788. this.callback(this.manager, eventType, {
  789. pointers: store,
  790. changedPointers: [ev],
  791. pointerType: pointerType,
  792. srcEvent: ev
  793. });
  794. if (removePointer) {
  795. // remove from the store
  796. store.splice(storeIndex, 1);
  797. }
  798. }
  799. });
  800. var SINGLE_TOUCH_INPUT_MAP = {
  801. touchstart: INPUT_START,
  802. touchmove: INPUT_MOVE,
  803. touchend: INPUT_END,
  804. touchcancel: INPUT_CANCEL
  805. };
  806. var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
  807. var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
  808. /**
  809. * Touch events input
  810. * @constructor
  811. * @extends Input
  812. */
  813. function SingleTouchInput() {
  814. this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
  815. this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
  816. this.started = false;
  817. Input.apply(this, arguments);
  818. }
  819. inherit(SingleTouchInput, Input, {
  820. handler: function TEhandler(ev) {
  821. var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
  822. // should we handle the touch events?
  823. if (type === INPUT_START) {
  824. this.started = true;
  825. }
  826. if (!this.started) {
  827. return;
  828. }
  829. var touches = normalizeSingleTouches.call(this, ev, type);
  830. // when done, reset the started state
  831. if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
  832. this.started = false;
  833. }
  834. this.callback(this.manager, type, {
  835. pointers: touches[0],
  836. changedPointers: touches[1],
  837. pointerType: INPUT_TYPE_TOUCH,
  838. srcEvent: ev
  839. });
  840. }
  841. });
  842. /**
  843. * @this {TouchInput}
  844. * @param {Object} ev
  845. * @param {Number} type flag
  846. * @returns {undefined|Array} [all, changed]
  847. */
  848. function normalizeSingleTouches(ev, type) {
  849. var all = toArray(ev.touches);
  850. var changed = toArray(ev.changedTouches);
  851. if (type & (INPUT_END | INPUT_CANCEL)) {
  852. all = uniqueArray(all.concat(changed), 'identifier', true);
  853. }
  854. return [all, changed];
  855. }
  856. var TOUCH_INPUT_MAP = {
  857. touchstart: INPUT_START,
  858. touchmove: INPUT_MOVE,
  859. touchend: INPUT_END,
  860. touchcancel: INPUT_CANCEL
  861. };
  862. var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
  863. /**
  864. * Multi-user touch events input
  865. * @constructor
  866. * @extends Input
  867. */
  868. function TouchInput() {
  869. this.evTarget = TOUCH_TARGET_EVENTS;
  870. this.targetIds = {};
  871. Input.apply(this, arguments);
  872. }
  873. inherit(TouchInput, Input, {
  874. handler: function MTEhandler(ev) {
  875. var type = TOUCH_INPUT_MAP[ev.type];
  876. var touches = getTouches.call(this, ev, type);
  877. if (!touches) {
  878. return;
  879. }
  880. this.callback(this.manager, type, {
  881. pointers: touches[0],
  882. changedPointers: touches[1],
  883. pointerType: INPUT_TYPE_TOUCH,
  884. srcEvent: ev
  885. });
  886. }
  887. });
  888. /**
  889. * @this {TouchInput}
  890. * @param {Object} ev
  891. * @param {Number} type flag
  892. * @returns {undefined|Array} [all, changed]
  893. */
  894. function getTouches(ev, type) {
  895. var allTouches = toArray(ev.touches);
  896. var targetIds = this.targetIds;
  897. // when there is only one touch, the process can be simplified
  898. if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
  899. targetIds[allTouches[0].identifier] = true;
  900. return [allTouches, allTouches];
  901. }
  902. var i,
  903. targetTouches,
  904. changedTouches = toArray(ev.changedTouches),
  905. changedTargetTouches = [],
  906. target = this.target;
  907. // get target touches from touches
  908. targetTouches = allTouches.filter(function(touch) {
  909. return hasParent(touch.target, target);
  910. });
  911. // collect touches
  912. if (type === INPUT_START) {
  913. i = 0;
  914. while (i < targetTouches.length) {
  915. targetIds[targetTouches[i].identifier] = true;
  916. i++;
  917. }
  918. }
  919. // filter changed touches to only contain touches that exist in the collected target ids
  920. i = 0;
  921. while (i < changedTouches.length) {
  922. if (targetIds[changedTouches[i].identifier]) {
  923. changedTargetTouches.push(changedTouches[i]);
  924. }
  925. // cleanup removed touches
  926. if (type & (INPUT_END | INPUT_CANCEL)) {
  927. delete targetIds[changedTouches[i].identifier];
  928. }
  929. i++;
  930. }
  931. if (!changedTargetTouches.length) {
  932. return;
  933. }
  934. return [
  935. // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
  936. uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
  937. changedTargetTouches
  938. ];
  939. }
  940. /**
  941. * Combined touch and mouse input
  942. *
  943. * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
  944. * This because touch devices also emit mouse events while doing a touch.
  945. *
  946. * @constructor
  947. * @extends Input
  948. */
  949. var DEDUP_TIMEOUT = 2500;
  950. var DEDUP_DISTANCE = 25;
  951. function TouchMouseInput() {
  952. Input.apply(this, arguments);
  953. var handler = bindFn(this.handler, this);
  954. this.touch = new TouchInput(this.manager, handler);
  955. this.mouse = new MouseInput(this.manager, handler);
  956. this.primaryTouch = null;
  957. this.lastTouches = [];
  958. }
  959. inherit(TouchMouseInput, Input, {
  960. /**
  961. * handle mouse and touch events
  962. * @param {Hammer} manager
  963. * @param {String} inputEvent
  964. * @param {Object} inputData
  965. */
  966. handler: function TMEhandler(manager, inputEvent, inputData) {
  967. var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
  968. isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
  969. if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
  970. return;
  971. }
  972. // when we're in a touch event, record touches to de-dupe synthetic mouse event
  973. if (isTouch) {
  974. recordTouches.call(this, inputEvent, inputData);
  975. } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
  976. return;
  977. }
  978. this.callback(manager, inputEvent, inputData);
  979. },
  980. /**
  981. * remove the event listeners
  982. */
  983. destroy: function destroy() {
  984. this.touch.destroy();
  985. this.mouse.destroy();
  986. }
  987. });
  988. function recordTouches(eventType, eventData) {
  989. if (eventType & INPUT_START) {
  990. this.primaryTouch = eventData.changedPointers[0].identifier;
  991. setLastTouch.call(this, eventData);
  992. } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
  993. setLastTouch.call(this, eventData);
  994. }
  995. }
  996. function setLastTouch(eventData) {
  997. var touch = eventData.changedPointers[0];
  998. if (touch.identifier === this.primaryTouch) {
  999. var lastTouch = {x: touch.clientX, y: touch.clientY};
  1000. this.lastTouches.push(lastTouch);
  1001. var lts = this.lastTouches;
  1002. var removeLastTouch = function() {
  1003. var i = lts.indexOf(lastTouch);
  1004. if (i > -1) {
  1005. lts.splice(i, 1);
  1006. }
  1007. };
  1008. setTimeout(removeLastTouch, DEDUP_TIMEOUT);
  1009. }
  1010. }
  1011. function isSyntheticEvent(eventData) {
  1012. var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
  1013. for (var i = 0; i < this.lastTouches.length; i++) {
  1014. var t = this.lastTouches[i];
  1015. var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
  1016. if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
  1017. return true;
  1018. }
  1019. }
  1020. return false;
  1021. }
  1022. var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
  1023. var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
  1024. // magical touchAction value
  1025. var TOUCH_ACTION_COMPUTE = 'compute';
  1026. var TOUCH_ACTION_AUTO = 'auto';
  1027. var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
  1028. var TOUCH_ACTION_NONE = 'none';
  1029. var TOUCH_ACTION_PAN_X = 'pan-x';
  1030. var TOUCH_ACTION_PAN_Y = 'pan-y';
  1031. var TOUCH_ACTION_MAP = getTouchActionProps();
  1032. /**
  1033. * Touch Action
  1034. * sets the touchAction property or uses the js alternative
  1035. * @param {Manager} manager
  1036. * @param {String} value
  1037. * @constructor
  1038. */
  1039. function TouchAction(manager, value) {
  1040. this.manager = manager;
  1041. this.set(value);
  1042. }
  1043. TouchAction.prototype = {
  1044. /**
  1045. * set the touchAction value on the element or enable the polyfill
  1046. * @param {String} value
  1047. */
  1048. set: function(value) {
  1049. // find out the touch-action by the event handlers
  1050. if (value == TOUCH_ACTION_COMPUTE) {
  1051. value = this.compute();
  1052. }
  1053. if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
  1054. this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
  1055. }
  1056. this.actions = value.toLowerCase().trim();
  1057. },
  1058. /**
  1059. * just re-set the touchAction value
  1060. */
  1061. update: function() {
  1062. this.set(this.manager.options.touchAction);
  1063. },
  1064. /**
  1065. * compute the value for the touchAction property based on the recognizer's settings
  1066. * @returns {String} value
  1067. */
  1068. compute: function() {
  1069. var actions = [];
  1070. each(this.manager.recognizers, function(recognizer) {
  1071. if (boolOrFn(recognizer.options.enable, [recognizer])) {
  1072. actions = actions.concat(recognizer.getTouchAction());
  1073. }
  1074. });
  1075. return cleanTouchActions(actions.join(' '));
  1076. },
  1077. /**
  1078. * this method is called on each input cycle and provides the preventing of the browser behavior
  1079. * @param {Object} input
  1080. */
  1081. preventDefaults: function(input) {
  1082. var srcEvent = input.srcEvent;
  1083. var direction = input.offsetDirection;
  1084. // if the touch action did prevented once this session
  1085. if (this.manager.session.prevented) {
  1086. srcEvent.preventDefault();
  1087. return;
  1088. }
  1089. var actions = this.actions;
  1090. var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
  1091. var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
  1092. var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
  1093. if (hasNone) {
  1094. //do not prevent defaults if this is a tap gesture
  1095. var isTapPointer = input.pointers.length === 1;
  1096. var isTapMovement = input.distance < 2;
  1097. var isTapTouchTime = input.deltaTime < 250;
  1098. if (isTapPointer && isTapMovement && isTapTouchTime) {
  1099. return;
  1100. }
  1101. }
  1102. if (hasPanX && hasPanY) {
  1103. // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
  1104. return;
  1105. }
  1106. if (hasNone ||
  1107. (hasPanY && direction & DIRECTION_HORIZONTAL) ||
  1108. (hasPanX && direction & DIRECTION_VERTICAL)) {
  1109. return this.preventSrc(srcEvent);
  1110. }
  1111. },
  1112. /**
  1113. * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
  1114. * @param {Object} srcEvent
  1115. */
  1116. preventSrc: function(srcEvent) {
  1117. this.manager.session.prevented = true;
  1118. srcEvent.preventDefault();
  1119. }
  1120. };
  1121. /**
  1122. * when the touchActions are collected they are not a valid value, so we need to clean things up. *
  1123. * @param {String} actions
  1124. * @returns {*}
  1125. */
  1126. function cleanTouchActions(actions) {
  1127. // none
  1128. if (inStr(actions, TOUCH_ACTION_NONE)) {
  1129. return TOUCH_ACTION_NONE;
  1130. }
  1131. var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
  1132. var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
  1133. // if both pan-x and pan-y are set (different recognizers
  1134. // for different directions, e.g. horizontal pan but vertical swipe?)
  1135. // we need none (as otherwise with pan-x pan-y combined none of these
  1136. // recognizers will work, since the browser would handle all panning
  1137. if (hasPanX && hasPanY) {
  1138. return TOUCH_ACTION_NONE;
  1139. }
  1140. // pan-x OR pan-y
  1141. if (hasPanX || hasPanY) {
  1142. return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
  1143. }
  1144. // manipulation
  1145. if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
  1146. return TOUCH_ACTION_MANIPULATION;
  1147. }
  1148. return TOUCH_ACTION_AUTO;
  1149. }
  1150. function getTouchActionProps() {
  1151. if (!NATIVE_TOUCH_ACTION) {
  1152. return false;
  1153. }
  1154. var touchMap = {};
  1155. var cssSupports = window.CSS && window.CSS.supports;
  1156. ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
  1157. // If css.supports is not supported but there is native touch-action assume it supports
  1158. // all values. This is the case for IE 10 and 11.
  1159. touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
  1160. });
  1161. return touchMap;
  1162. }
  1163. /**
  1164. * Recognizer flow explained; *
  1165. * All recognizers have the initial state of POSSIBLE when a input session starts.
  1166. * The definition of a input session is from the first input until the last input, with all it's movement in it. *
  1167. * Example session for mouse-input: mousedown -> mousemove -> mouseup
  1168. *
  1169. * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
  1170. * which determines with state it should be.
  1171. *
  1172. * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
  1173. * POSSIBLE to give it another change on the next cycle.
  1174. *
  1175. * Possible
  1176. * |
  1177. * +-----+---------------+
  1178. * | |
  1179. * +-----+-----+ |
  1180. * | | |
  1181. * Failed Cancelled |
  1182. * +-------+------+
  1183. * | |
  1184. * Recognized Began
  1185. * |
  1186. * Changed
  1187. * |
  1188. * Ended/Recognized
  1189. */
  1190. var STATE_POSSIBLE = 1;
  1191. var STATE_BEGAN = 2;
  1192. var STATE_CHANGED = 4;
  1193. var STATE_ENDED = 8;
  1194. var STATE_RECOGNIZED = STATE_ENDED;
  1195. var STATE_CANCELLED = 16;
  1196. var STATE_FAILED = 32;
  1197. /**
  1198. * Recognizer
  1199. * Every recognizer needs to extend from this class.
  1200. * @constructor
  1201. * @param {Object} options
  1202. */
  1203. function Recognizer(options) {
  1204. this.options = assign({}, this.defaults, options || {});
  1205. this.id = uniqueId();
  1206. this.manager = null;
  1207. // default is enable true
  1208. this.options.enable = ifUndefined(this.options.enable, true);
  1209. this.state = STATE_POSSIBLE;
  1210. this.simultaneous = {};
  1211. this.requireFail = [];
  1212. }
  1213. Recognizer.prototype = {
  1214. /**
  1215. * @virtual
  1216. * @type {Object}
  1217. */
  1218. defaults: {},
  1219. /**
  1220. * set options
  1221. * @param {Object} options
  1222. * @return {Recognizer}
  1223. */
  1224. set: function(options) {
  1225. assign(this.options, options);
  1226. // also update the touchAction, in case something changed about the directions/enabled state
  1227. this.manager && this.manager.touchAction.update();
  1228. return this;
  1229. },
  1230. /**
  1231. * recognize simultaneous with an other recognizer.
  1232. * @param {Recognizer} otherRecognizer
  1233. * @returns {Recognizer} this
  1234. */
  1235. recognizeWith: function(otherRecognizer) {
  1236. if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
  1237. return this;
  1238. }
  1239. var simultaneous = this.simultaneous;
  1240. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  1241. if (!simultaneous[otherRecognizer.id]) {
  1242. simultaneous[otherRecognizer.id] = otherRecognizer;
  1243. otherRecognizer.recognizeWith(this);
  1244. }
  1245. return this;
  1246. },
  1247. /**
  1248. * drop the simultaneous link. it doesnt remove the link on the other recognizer.
  1249. * @param {Recognizer} otherRecognizer
  1250. * @returns {Recognizer} this
  1251. */
  1252. dropRecognizeWith: function(otherRecognizer) {
  1253. if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
  1254. return this;
  1255. }
  1256. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  1257. delete this.simultaneous[otherRecognizer.id];
  1258. return this;
  1259. },
  1260. /**
  1261. * recognizer can only run when an other is failing
  1262. * @param {Recognizer} otherRecognizer
  1263. * @returns {Recognizer} this
  1264. */
  1265. requireFailure: function(otherRecognizer) {
  1266. if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
  1267. return this;
  1268. }
  1269. var requireFail = this.requireFail;
  1270. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  1271. if (inArray(requireFail, otherRecognizer) === -1) {
  1272. requireFail.push(otherRecognizer);
  1273. otherRecognizer.requireFailure(this);
  1274. }
  1275. return this;
  1276. },
  1277. /**
  1278. * drop the requireFailure link. it does not remove the link on the other recognizer.
  1279. * @param {Recognizer} otherRecognizer
  1280. * @returns {Recognizer} this
  1281. */
  1282. dropRequireFailure: function(otherRecognizer) {
  1283. if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
  1284. return this;
  1285. }
  1286. otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
  1287. var index = inArray(this.requireFail, otherRecognizer);
  1288. if (index > -1) {
  1289. this.requireFail.splice(index, 1);
  1290. }
  1291. return this;
  1292. },
  1293. /**
  1294. * has require failures boolean
  1295. * @returns {boolean}
  1296. */
  1297. hasRequireFailures: function() {
  1298. return this.requireFail.length > 0;
  1299. },
  1300. /**
  1301. * if the recognizer can recognize simultaneous with an other recognizer
  1302. * @param {Recognizer} otherRecognizer
  1303. * @returns {Boolean}
  1304. */
  1305. canRecognizeWith: function(otherRecognizer) {
  1306. return !!this.simultaneous[otherRecognizer.id];
  1307. },
  1308. /**
  1309. * You should use `tryEmit` instead of `emit` directly to check
  1310. * that all the needed recognizers has failed before emitting.
  1311. * @param {Object} input
  1312. */
  1313. emit: function(input) {
  1314. var self = this;
  1315. var state = this.state;
  1316. function emit(event) {
  1317. self.manager.emit(event, input);
  1318. }
  1319. // 'panstart' and 'panmove'
  1320. if (state < STATE_ENDED) {
  1321. emit(self.options.event + stateStr(state));
  1322. }
  1323. emit(self.options.event); // simple 'eventName' events
  1324. if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
  1325. emit(input.additionalEvent);
  1326. }
  1327. // panend and pancancel
  1328. if (state >= STATE_ENDED) {
  1329. emit(self.options.event + stateStr(state));
  1330. }
  1331. },
  1332. /**
  1333. * Check that all the require failure recognizers has failed,
  1334. * if true, it emits a gesture event,
  1335. * otherwise, setup the state to FAILED.
  1336. * @param {Object} input
  1337. */
  1338. tryEmit: function(input) {
  1339. if (this.canEmit()) {
  1340. return this.emit(input);
  1341. }
  1342. // it's failing anyway
  1343. this.state = STATE_FAILED;
  1344. },
  1345. /**
  1346. * can we emit?
  1347. * @returns {boolean}
  1348. */
  1349. canEmit: function() {
  1350. var i = 0;
  1351. while (i < this.requireFail.length) {
  1352. if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
  1353. return false;
  1354. }
  1355. i++;
  1356. }
  1357. return true;
  1358. },
  1359. /**
  1360. * update the recognizer
  1361. * @param {Object} inputData
  1362. */
  1363. recognize: function(inputData) {
  1364. // make a new copy of the inputData
  1365. // so we can change the inputData without messing up the other recognizers
  1366. var inputDataClone = assign({}, inputData);
  1367. // is is enabled and allow recognizing?
  1368. if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
  1369. this.reset();
  1370. this.state = STATE_FAILED;
  1371. return;
  1372. }
  1373. // reset when we've reached the end
  1374. if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
  1375. this.state = STATE_POSSIBLE;
  1376. }
  1377. this.state = this.process(inputDataClone);
  1378. // the recognizer has recognized a gesture
  1379. // so trigger an event
  1380. if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
  1381. this.tryEmit(inputDataClone);
  1382. }
  1383. },
  1384. /**
  1385. * return the state of the recognizer
  1386. * the actual recognizing happens in this method
  1387. * @virtual
  1388. * @param {Object} inputData
  1389. * @returns {Const} STATE
  1390. */
  1391. process: function(inputData) { }, // jshint ignore:line
  1392. /**
  1393. * return the preferred touch-action
  1394. * @virtual
  1395. * @returns {Array}
  1396. */
  1397. getTouchAction: function() { },
  1398. /**
  1399. * called when the gesture isn't allowed to recognize
  1400. * like when another is being recognized or it is disabled
  1401. * @virtual
  1402. */
  1403. reset: function() { }
  1404. };
  1405. /**
  1406. * get a usable string, used as event postfix
  1407. * @param {Const} state
  1408. * @returns {String} state
  1409. */
  1410. function stateStr(state) {
  1411. if (state & STATE_CANCELLED) {
  1412. return 'cancel';
  1413. } else if (state & STATE_ENDED) {
  1414. return 'end';
  1415. } else if (state & STATE_CHANGED) {
  1416. return 'move';
  1417. } else if (state & STATE_BEGAN) {
  1418. return 'start';
  1419. }
  1420. return '';
  1421. }
  1422. /**
  1423. * direction cons to string
  1424. * @param {Const} direction
  1425. * @returns {String}
  1426. */
  1427. function directionStr(direction) {
  1428. if (direction == DIRECTION_DOWN) {
  1429. return 'down';
  1430. } else if (direction == DIRECTION_UP) {
  1431. return 'up';
  1432. } else if (direction == DIRECTION_LEFT) {
  1433. return 'left';
  1434. } else if (direction == DIRECTION_RIGHT) {
  1435. return 'right';
  1436. }
  1437. return '';
  1438. }
  1439. /**
  1440. * get a recognizer by name if it is bound to a manager
  1441. * @param {Recognizer|String} otherRecognizer
  1442. * @param {Recognizer} recognizer
  1443. * @returns {Recognizer}
  1444. */
  1445. function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
  1446. var manager = recognizer.manager;
  1447. if (manager) {
  1448. return manager.get(otherRecognizer);
  1449. }
  1450. return otherRecognizer;
  1451. }
  1452. /**
  1453. * This recognizer is just used as a base for the simple attribute recognizers.
  1454. * @constructor
  1455. * @extends Recognizer
  1456. */
  1457. function AttrRecognizer() {
  1458. Recognizer.apply(this, arguments);
  1459. }
  1460. inherit(AttrRecognizer, Recognizer, {
  1461. /**
  1462. * @namespace
  1463. * @memberof AttrRecognizer
  1464. */
  1465. defaults: {
  1466. /**
  1467. * @type {Number}
  1468. * @default 1
  1469. */
  1470. pointers: 1
  1471. },
  1472. /**
  1473. * Used to check if it the recognizer receives valid input, like input.distance > 10.
  1474. * @memberof AttrRecognizer
  1475. * @param {Object} input
  1476. * @returns {Boolean} recognized
  1477. */
  1478. attrTest: function(input) {
  1479. var optionPointers = this.options.pointers;
  1480. return optionPointers === 0 || input.pointers.length === optionPointers;
  1481. },
  1482. /**
  1483. * Process the input and return the state for the recognizer
  1484. * @memberof AttrRecognizer
  1485. * @param {Object} input
  1486. * @returns {*} State
  1487. */
  1488. process: function(input) {
  1489. var state = this.state;
  1490. var eventType = input.eventType;
  1491. var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
  1492. var isValid = this.attrTest(input);
  1493. // on cancel input and we've recognized before, return STATE_CANCELLED
  1494. if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
  1495. return state | STATE_CANCELLED;
  1496. } else if (isRecognized || isValid) {
  1497. if (eventType & INPUT_END) {
  1498. return state | STATE_ENDED;
  1499. } else if (!(state & STATE_BEGAN)) {
  1500. return STATE_BEGAN;
  1501. }
  1502. return state | STATE_CHANGED;
  1503. }
  1504. return STATE_FAILED;
  1505. }
  1506. });
  1507. /**
  1508. * Pan
  1509. * Recognized when the pointer is down and moved in the allowed direction.
  1510. * @constructor
  1511. * @extends AttrRecognizer
  1512. */
  1513. function PanRecognizer() {
  1514. AttrRecognizer.apply(this, arguments);
  1515. this.pX = null;
  1516. this.pY = null;
  1517. }
  1518. inherit(PanRecognizer, AttrRecognizer, {
  1519. /**
  1520. * @namespace
  1521. * @memberof PanRecognizer
  1522. */
  1523. defaults: {
  1524. event: 'pan',
  1525. threshold: 10,
  1526. pointers: 1,
  1527. direction: DIRECTION_ALL
  1528. },
  1529. getTouchAction: function() {
  1530. var direction = this.options.direction;
  1531. var actions = [];
  1532. if (direction & DIRECTION_HORIZONTAL) {
  1533. actions.push(TOUCH_ACTION_PAN_Y);
  1534. }
  1535. if (direction & DIRECTION_VERTICAL) {
  1536. actions.push(TOUCH_ACTION_PAN_X);
  1537. }
  1538. return actions;
  1539. },
  1540. directionTest: function(input) {
  1541. var options = this.options;
  1542. var hasMoved = true;
  1543. var distance = input.distance;
  1544. var direction = input.direction;
  1545. var x = input.deltaX;
  1546. var y = input.deltaY;
  1547. // lock to axis?
  1548. if (!(direction & options.direction)) {
  1549. if (options.direction & DIRECTION_HORIZONTAL) {
  1550. direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  1551. hasMoved = x != this.pX;
  1552. distance = Math.abs(input.deltaX);
  1553. } else {
  1554. direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  1555. hasMoved = y != this.pY;
  1556. distance = Math.abs(input.deltaY);
  1557. }
  1558. }
  1559. input.direction = direction;
  1560. return hasMoved && distance > options.threshold && direction & options.direction;
  1561. },
  1562. attrTest: function(input) {
  1563. return AttrRecognizer.prototype.attrTest.call(this, input) &&
  1564. (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
  1565. },
  1566. emit: function(input) {
  1567. this.pX = input.deltaX;
  1568. this.pY = input.deltaY;
  1569. var direction = directionStr(input.direction);
  1570. if (direction) {
  1571. input.additionalEvent = this.options.event + direction;
  1572. }
  1573. this._super.emit.call(this, input);
  1574. }
  1575. });
  1576. /**
  1577. * Pinch
  1578. * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
  1579. * @constructor
  1580. * @extends AttrRecognizer
  1581. */
  1582. function PinchRecognizer() {
  1583. AttrRecognizer.apply(this, arguments);
  1584. }
  1585. inherit(PinchRecognizer, AttrRecognizer, {
  1586. /**
  1587. * @namespace
  1588. * @memberof PinchRecognizer
  1589. */
  1590. defaults: {
  1591. event: 'pinch',
  1592. threshold: 0,
  1593. pointers: 2
  1594. },
  1595. getTouchAction: function() {
  1596. return [TOUCH_ACTION_NONE];
  1597. },
  1598. attrTest: function(input) {
  1599. return this._super.attrTest.call(this, input) &&
  1600. (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
  1601. },
  1602. emit: function(input) {
  1603. if (input.scale !== 1) {
  1604. var inOut = input.scale < 1 ? 'in' : 'out';
  1605. input.additionalEvent = this.options.event + inOut;
  1606. }
  1607. this._super.emit.call(this, input);
  1608. }
  1609. });
  1610. /**
  1611. * Press
  1612. * Recognized when the pointer is down for x ms without any movement.
  1613. * @constructor
  1614. * @extends Recognizer
  1615. */
  1616. function PressRecognizer() {
  1617. Recognizer.apply(this, arguments);
  1618. this._timer = null;
  1619. this._input = null;
  1620. }
  1621. inherit(PressRecognizer, Recognizer, {
  1622. /**
  1623. * @namespace
  1624. * @memberof PressRecognizer
  1625. */
  1626. defaults: {
  1627. event: 'press',
  1628. pointers: 1,
  1629. time: 251, // minimal time of the pointer to be pressed
  1630. threshold: 9 // a minimal movement is ok, but keep it low
  1631. },
  1632. getTouchAction: function() {
  1633. return [TOUCH_ACTION_AUTO];
  1634. },
  1635. process: function(input) {
  1636. var options = this.options;
  1637. var validPointers = input.pointers.length === options.pointers;
  1638. var validMovement = input.distance < options.threshold;
  1639. var validTime = input.deltaTime > options.time;
  1640. this._input = input;
  1641. // we only allow little movement
  1642. // and we've reached an end event, so a tap is possible
  1643. if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
  1644. this.reset();
  1645. } else if (input.eventType & INPUT_START) {
  1646. this.reset();
  1647. this._timer = setTimeoutContext(function() {
  1648. this.state = STATE_RECOGNIZED;
  1649. this.tryEmit();
  1650. }, options.time, this);
  1651. } else if (input.eventType & INPUT_END) {
  1652. return STATE_RECOGNIZED;
  1653. }
  1654. return STATE_FAILED;
  1655. },
  1656. reset: function() {
  1657. clearTimeout(this._timer);
  1658. },
  1659. emit: function(input) {
  1660. if (this.state !== STATE_RECOGNIZED) {
  1661. return;
  1662. }
  1663. if (input && (input.eventType & INPUT_END)) {
  1664. this.manager.emit(this.options.event + 'up', input);
  1665. } else {
  1666. this._input.timeStamp = now();
  1667. this.manager.emit(this.options.event, this._input);
  1668. }
  1669. }
  1670. });
  1671. /**
  1672. * Rotate
  1673. * Recognized when two or more pointer are moving in a circular motion.
  1674. * @constructor
  1675. * @extends AttrRecognizer
  1676. */
  1677. function RotateRecognizer() {
  1678. AttrRecognizer.apply(this, arguments);
  1679. }
  1680. inherit(RotateRecognizer, AttrRecognizer, {
  1681. /**
  1682. * @namespace
  1683. * @memberof RotateRecognizer
  1684. */
  1685. defaults: {
  1686. event: 'rotate',
  1687. threshold: 0,
  1688. pointers: 2
  1689. },
  1690. getTouchAction: function() {
  1691. return [TOUCH_ACTION_NONE];
  1692. },
  1693. attrTest: function(input) {
  1694. return this._super.attrTest.call(this, input) &&
  1695. (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
  1696. }
  1697. });
  1698. /**
  1699. * Swipe
  1700. * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
  1701. * @constructor
  1702. * @extends AttrRecognizer
  1703. */
  1704. function SwipeRecognizer() {
  1705. AttrRecognizer.apply(this, arguments);
  1706. }
  1707. inherit(SwipeRecognizer, AttrRecognizer, {
  1708. /**
  1709. * @namespace
  1710. * @memberof SwipeRecognizer
  1711. */
  1712. defaults: {
  1713. event: 'swipe',
  1714. threshold: 10,
  1715. velocity: 0.3,
  1716. direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
  1717. pointers: 1
  1718. },
  1719. getTouchAction: function() {
  1720. return PanRecognizer.prototype.getTouchAction.call(this);
  1721. },
  1722. attrTest: function(input) {
  1723. var direction = this.options.direction;
  1724. var velocity;
  1725. if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
  1726. velocity = input.overallVelocity;
  1727. } else if (direction & DIRECTION_HORIZONTAL) {
  1728. velocity = input.overallVelocityX;
  1729. } else if (direction & DIRECTION_VERTICAL) {
  1730. velocity = input.overallVelocityY;
  1731. }
  1732. return this._super.attrTest.call(this, input) &&
  1733. direction & input.offsetDirection &&
  1734. input.distance > this.options.threshold &&
  1735. input.maxPointers == this.options.pointers &&
  1736. abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
  1737. },
  1738. emit: function(input) {
  1739. var direction = directionStr(input.offsetDirection);
  1740. if (direction) {
  1741. this.manager.emit(this.options.event + direction, input);
  1742. }
  1743. this.manager.emit(this.options.event, input);
  1744. }
  1745. });
  1746. /**
  1747. * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
  1748. * between the given interval and position. The delay option can be used to recognize multi-taps without firing
  1749. * a single tap.
  1750. *
  1751. * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
  1752. * multi-taps being recognized.
  1753. * @constructor
  1754. * @extends Recognizer
  1755. */
  1756. function TapRecognizer() {
  1757. Recognizer.apply(this, arguments);
  1758. // previous time and center,
  1759. // used for tap counting
  1760. this.pTime = false;
  1761. this.pCenter = false;
  1762. this._timer = null;
  1763. this._input = null;
  1764. this.count = 0;
  1765. }
  1766. inherit(TapRecognizer, Recognizer, {
  1767. /**
  1768. * @namespace
  1769. * @memberof PinchRecognizer
  1770. */
  1771. defaults: {
  1772. event: 'tap',
  1773. pointers: 1,
  1774. taps: 1,
  1775. interval: 300, // max time between the multi-tap taps
  1776. time: 250, // max time of the pointer to be down (like finger on the screen)
  1777. threshold: 9, // a minimal movement is ok, but keep it low
  1778. posThreshold: 10 // a multi-tap can be a bit off the initial position
  1779. },
  1780. getTouchAction: function() {
  1781. return [TOUCH_ACTION_MANIPULATION];
  1782. },
  1783. process: function(input) {
  1784. var options = this.options;
  1785. var validPointers = input.pointers.length === options.pointers;
  1786. var validMovement = input.distance < options.threshold;
  1787. var validTouchTime = input.deltaTime < options.time;
  1788. this.reset();
  1789. if ((input.eventType & INPUT_START) && (this.count === 0)) {
  1790. return this.failTimeout();
  1791. }
  1792. // we only allow little movement
  1793. // and we've reached an end event, so a tap is possible
  1794. if (validMovement && validTouchTime && validPointers) {
  1795. if (input.eventType != INPUT_END) {
  1796. return this.failTimeout();
  1797. }
  1798. var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
  1799. var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
  1800. this.pTime = input.timeStamp;
  1801. this.pCenter = input.center;
  1802. if (!validMultiTap || !validInterval) {
  1803. this.count = 1;
  1804. } else {
  1805. this.count += 1;
  1806. }
  1807. this._input = input;
  1808. // if tap count matches we have recognized it,
  1809. // else it has began recognizing...
  1810. var tapCount = this.count % options.taps;
  1811. if (tapCount === 0) {
  1812. // no failing requirements, immediately trigger the tap event
  1813. // or wait as long as the multitap interval to trigger
  1814. if (!this.hasRequireFailures()) {
  1815. return STATE_RECOGNIZED;
  1816. } else {
  1817. this._timer = setTimeoutContext(function() {
  1818. this.state = STATE_RECOGNIZED;
  1819. this.tryEmit();
  1820. }, options.interval, this);
  1821. return STATE_BEGAN;
  1822. }
  1823. }
  1824. }
  1825. return STATE_FAILED;
  1826. },
  1827. failTimeout: function() {
  1828. this._timer = setTimeoutContext(function() {
  1829. this.state = STATE_FAILED;
  1830. }, this.options.interval, this);
  1831. return STATE_FAILED;
  1832. },
  1833. reset: function() {
  1834. clearTimeout(this._timer);
  1835. },
  1836. emit: function() {
  1837. if (this.state == STATE_RECOGNIZED) {
  1838. this._input.tapCount = this.count;
  1839. this.manager.emit(this.options.event, this._input);
  1840. }
  1841. }
  1842. });
  1843. /**
  1844. * Simple way to create a manager with a default set of recognizers.
  1845. * @param {HTMLElement} element
  1846. * @param {Object} [options]
  1847. * @constructor
  1848. */
  1849. function Hammer(element, options) {
  1850. options = options || {};
  1851. options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
  1852. return new Manager(element, options);
  1853. }
  1854. /**
  1855. * @const {string}
  1856. */
  1857. Hammer.VERSION = '2.0.7';
  1858. /**
  1859. * default settings
  1860. * @namespace
  1861. */
  1862. Hammer.defaults = {
  1863. /**
  1864. * set if DOM events are being triggered.
  1865. * But this is slower and unused by simple implementations, so disabled by default.
  1866. * @type {Boolean}
  1867. * @default false
  1868. */
  1869. domEvents: false,
  1870. /**
  1871. * The value for the touchAction property/fallback.
  1872. * When set to `compute` it will magically set the correct value based on the added recognizers.
  1873. * @type {String}
  1874. * @default compute
  1875. */
  1876. touchAction: TOUCH_ACTION_COMPUTE,
  1877. /**
  1878. * @type {Boolean}
  1879. * @default true
  1880. */
  1881. enable: true,
  1882. /**
  1883. * EXPERIMENTAL FEATURE -- can be removed/changed
  1884. * Change the parent input target element.
  1885. * If Null, then it is being set the to main element.
  1886. * @type {Null|EventTarget}
  1887. * @default null
  1888. */
  1889. inputTarget: null,
  1890. /**
  1891. * force an input class
  1892. * @type {Null|Function}
  1893. * @default null
  1894. */
  1895. inputClass: null,
  1896. /**
  1897. * Default recognizer setup when calling `Hammer()`
  1898. * When creating a new Manager these will be skipped.
  1899. * @type {Array}
  1900. */
  1901. preset: [
  1902. // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
  1903. [RotateRecognizer, {enable: false}],
  1904. [PinchRecognizer, {enable: false}, ['rotate']],
  1905. [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
  1906. [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
  1907. [TapRecognizer],
  1908. [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
  1909. [PressRecognizer]
  1910. ],
  1911. /**
  1912. * Some CSS properties can be used to improve the working of Hammer.
  1913. * Add them to this method and they will be set when creating a new Manager.
  1914. * @namespace
  1915. */
  1916. cssProps: {
  1917. /**
  1918. * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
  1919. * @type {String}
  1920. * @default 'none'
  1921. */
  1922. userSelect: 'none',
  1923. /**
  1924. * Disable the Windows Phone grippers when pressing an element.
  1925. * @type {String}
  1926. * @default 'none'
  1927. */
  1928. touchSelect: 'none',
  1929. /**
  1930. * Disables the default callout shown when you touch and hold a touch target.
  1931. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  1932. * a callout containing information about the link. This property allows you to disable that callout.
  1933. * @type {String}
  1934. * @default 'none'
  1935. */
  1936. touchCallout: 'none',
  1937. /**
  1938. * Specifies whether zooming is enabled. Used by IE10>
  1939. * @type {String}
  1940. * @default 'none'
  1941. */
  1942. contentZooming: 'none',
  1943. /**
  1944. * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
  1945. * @type {String}
  1946. * @default 'none'
  1947. */
  1948. userDrag: 'none',
  1949. /**
  1950. * Overrides the highlight color shown when the user taps a link or a JavaScript
  1951. * clickable element in iOS. This property obeys the alpha value, if specified.
  1952. * @type {String}
  1953. * @default 'rgba(0,0,0,0)'
  1954. */
  1955. tapHighlightColor: 'rgba(0,0,0,0)'
  1956. }
  1957. };
  1958. var STOP = 1;
  1959. var FORCED_STOP = 2;
  1960. /**
  1961. * Manager
  1962. * @param {HTMLElement} element
  1963. * @param {Object} [options]
  1964. * @constructor
  1965. */
  1966. function Manager(element, options) {
  1967. this.options = assign({}, Hammer.defaults, options || {});
  1968. this.options.inputTarget = this.options.inputTarget || element;
  1969. this.handlers = {};
  1970. this.session = {};
  1971. this.recognizers = [];
  1972. this.oldCssProps = {};
  1973. this.element = element;
  1974. this.input = createInputInstance(this);
  1975. this.touchAction = new TouchAction(this, this.options.touchAction);
  1976. toggleCssProps(this, true);
  1977. each(this.options.recognizers, function(item) {
  1978. var recognizer = this.add(new (item[0])(item[1]));
  1979. item[2] && recognizer.recognizeWith(item[2]);
  1980. item[3] && recognizer.requireFailure(item[3]);
  1981. }, this);
  1982. }
  1983. Manager.prototype = {
  1984. /**
  1985. * set options
  1986. * @param {Object} options
  1987. * @returns {Manager}
  1988. */
  1989. set: function(options) {
  1990. assign(this.options, options);
  1991. // Options that need a little more setup
  1992. if (options.touchAction) {
  1993. this.touchAction.update();
  1994. }
  1995. if (options.inputTarget) {
  1996. // Clean up existing event listeners and reinitialize
  1997. this.input.destroy();
  1998. this.input.target = options.inputTarget;
  1999. this.input.init();
  2000. }
  2001. return this;
  2002. },
  2003. /**
  2004. * stop recognizing for this session.
  2005. * This session will be discarded, when a new [input]start event is fired.
  2006. * When forced, the recognizer cycle is stopped immediately.
  2007. * @param {Boolean} [force]
  2008. */
  2009. stop: function(force) {
  2010. this.session.stopped = force ? FORCED_STOP : STOP;
  2011. },
  2012. /**
  2013. * run the recognizers!
  2014. * called by the inputHandler function on every movement of the pointers (touches)
  2015. * it walks through all the recognizers and tries to detect the gesture that is being made
  2016. * @param {Object} inputData
  2017. */
  2018. recognize: function(inputData) {
  2019. var session = this.session;
  2020. if (session.stopped) {
  2021. return;
  2022. }
  2023. // run the touch-action polyfill
  2024. this.touchAction.preventDefaults(inputData);
  2025. var recognizer;
  2026. var recognizers = this.recognizers;
  2027. // this holds the recognizer that is being recognized.
  2028. // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
  2029. // if no recognizer is detecting a thing, it is set to `null`
  2030. var curRecognizer = session.curRecognizer;
  2031. // reset when the last recognizer is recognized
  2032. // or when we're in a new session
  2033. if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
  2034. curRecognizer = session.curRecognizer = null;
  2035. }
  2036. var i = 0;
  2037. while (i < recognizers.length) {
  2038. recognizer = recognizers[i];
  2039. // find out if we are allowed try to recognize the input for this one.
  2040. // 1. allow if the session is NOT forced stopped (see the .stop() method)
  2041. // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
  2042. // that is being recognized.
  2043. // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
  2044. // this can be setup with the `recognizeWith()` method on the recognizer.
  2045. if (session.stopped !== FORCED_STOP && ( // 1
  2046. !curRecognizer || recognizer == curRecognizer || // 2
  2047. recognizer.canRecognizeWith(curRecognizer))) { // 3
  2048. recognizer.recognize(inputData);
  2049. } else {
  2050. recognizer.reset();
  2051. }
  2052. // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
  2053. // current active recognizer. but only if we don't already have an active recognizer
  2054. if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
  2055. curRecognizer = session.curRecognizer = recognizer;
  2056. }
  2057. i++;
  2058. }
  2059. },
  2060. /**
  2061. * get a recognizer by its event name.
  2062. * @param {Recognizer|String} recognizer
  2063. * @returns {Recognizer|Null}
  2064. */
  2065. get: function(recognizer) {
  2066. if (recognizer instanceof Recognizer) {
  2067. return recognizer;
  2068. }
  2069. var recognizers = this.recognizers;
  2070. for (var i = 0; i < recognizers.length; i++) {
  2071. if (recognizers[i].options.event == recognizer) {
  2072. return recognizers[i];
  2073. }
  2074. }
  2075. return null;
  2076. },
  2077. /**
  2078. * add a recognizer to the manager
  2079. * existing recognizers with the same event name will be removed
  2080. * @param {Recognizer} recognizer
  2081. * @returns {Recognizer|Manager}
  2082. */
  2083. add: function(recognizer) {
  2084. if (invokeArrayArg(recognizer, 'add', this)) {
  2085. return this;
  2086. }
  2087. // remove existing
  2088. var existing = this.get(recognizer.options.event);
  2089. if (existing) {
  2090. this.remove(existing);
  2091. }
  2092. this.recognizers.push(recognizer);
  2093. recognizer.manager = this;
  2094. this.touchAction.update();
  2095. return recognizer;
  2096. },
  2097. /**
  2098. * remove a recognizer by name or instance
  2099. * @param {Recognizer|String} recognizer
  2100. * @returns {Manager}
  2101. */
  2102. remove: function(recognizer) {
  2103. if (invokeArrayArg(recognizer, 'remove', this)) {
  2104. return this;
  2105. }
  2106. recognizer = this.get(recognizer);
  2107. // let's make sure this recognizer exists
  2108. if (recognizer) {
  2109. var recognizers = this.recognizers;
  2110. var index = inArray(recognizers, recognizer);
  2111. if (index !== -1) {
  2112. recognizers.splice(index, 1);
  2113. this.touchAction.update();
  2114. }
  2115. }
  2116. return this;
  2117. },
  2118. /**
  2119. * bind event
  2120. * @param {String} events
  2121. * @param {Function} handler
  2122. * @returns {EventEmitter} this
  2123. */
  2124. on: function(events, handler) {
  2125. if (events === undefined) {
  2126. return;
  2127. }
  2128. if (handler === undefined) {
  2129. return;
  2130. }
  2131. var handlers = this.handlers;
  2132. each(splitStr(events), function(event) {
  2133. handlers[event] = handlers[event] || [];
  2134. handlers[event].push(handler);
  2135. });
  2136. return this;
  2137. },
  2138. /**
  2139. * unbind event, leave emit blank to remove all handlers
  2140. * @param {String} events
  2141. * @param {Function} [handler]
  2142. * @returns {EventEmitter} this
  2143. */
  2144. off: function(events, handler) {
  2145. if (events === undefined) {
  2146. return;
  2147. }
  2148. var handlers = this.handlers;
  2149. each(splitStr(events), function(event) {
  2150. if (!handler) {
  2151. delete handlers[event];
  2152. } else {
  2153. handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
  2154. }
  2155. });
  2156. return this;
  2157. },
  2158. /**
  2159. * emit event to the listeners
  2160. * @param {String} event
  2161. * @param {Object} data
  2162. */
  2163. emit: function(event, data) {
  2164. // we also want to trigger dom events
  2165. if (this.options.domEvents) {
  2166. triggerDomEvent(event, data);
  2167. }
  2168. // no handlers, so skip it all
  2169. var handlers = this.handlers[event] && this.handlers[event].slice();
  2170. if (!handlers || !handlers.length) {
  2171. return;
  2172. }
  2173. data.type = event;
  2174. data.preventDefault = function() {
  2175. data.srcEvent.preventDefault();
  2176. };
  2177. var i = 0;
  2178. while (i < handlers.length) {
  2179. handlers[i](data);
  2180. i++;
  2181. }
  2182. },
  2183. /**
  2184. * destroy the manager and unbinds all events
  2185. * it doesn't unbind dom events, that is the user own responsibility
  2186. */
  2187. destroy: function() {
  2188. this.element && toggleCssProps(this, false);
  2189. this.handlers = {};
  2190. this.session = {};
  2191. this.input.destroy();
  2192. this.element = null;
  2193. }
  2194. };
  2195. /**
  2196. * add/remove the css properties as defined in manager.options.cssProps
  2197. * @param {Manager} manager
  2198. * @param {Boolean} add
  2199. */
  2200. function toggleCssProps(manager, add) {
  2201. var element = manager.element;
  2202. if (!element.style) {
  2203. return;
  2204. }
  2205. var prop;
  2206. each(manager.options.cssProps, function(value, name) {
  2207. prop = prefixed(element.style, name);
  2208. if (add) {
  2209. manager.oldCssProps[prop] = element.style[prop];
  2210. element.style[prop] = value;
  2211. } else {
  2212. element.style[prop] = manager.oldCssProps[prop] || '';
  2213. }
  2214. });
  2215. if (!add) {
  2216. manager.oldCssProps = {};
  2217. }
  2218. }
  2219. /**
  2220. * trigger dom event
  2221. * @param {String} event
  2222. * @param {Object} data
  2223. */
  2224. function triggerDomEvent(event, data) {
  2225. var gestureEvent = document.createEvent('Event');
  2226. gestureEvent.initEvent(event, true, true);
  2227. gestureEvent.gesture = data;
  2228. data.target.dispatchEvent(gestureEvent);
  2229. }
  2230. assign(Hammer, {
  2231. INPUT_START: INPUT_START,
  2232. INPUT_MOVE: INPUT_MOVE,
  2233. INPUT_END: INPUT_END,
  2234. INPUT_CANCEL: INPUT_CANCEL,
  2235. STATE_POSSIBLE: STATE_POSSIBLE,
  2236. STATE_BEGAN: STATE_BEGAN,
  2237. STATE_CHANGED: STATE_CHANGED,
  2238. STATE_ENDED: STATE_ENDED,
  2239. STATE_RECOGNIZED: STATE_RECOGNIZED,
  2240. STATE_CANCELLED: STATE_CANCELLED,
  2241. STATE_FAILED: STATE_FAILED,
  2242. DIRECTION_NONE: DIRECTION_NONE,
  2243. DIRECTION_LEFT: DIRECTION_LEFT,
  2244. DIRECTION_RIGHT: DIRECTION_RIGHT,
  2245. DIRECTION_UP: DIRECTION_UP,
  2246. DIRECTION_DOWN: DIRECTION_DOWN,
  2247. DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
  2248. DIRECTION_VERTICAL: DIRECTION_VERTICAL,
  2249. DIRECTION_ALL: DIRECTION_ALL,
  2250. Manager: Manager,
  2251. Input: Input,
  2252. TouchAction: TouchAction,
  2253. TouchInput: TouchInput,
  2254. MouseInput: MouseInput,
  2255. PointerEventInput: PointerEventInput,
  2256. TouchMouseInput: TouchMouseInput,
  2257. SingleTouchInput: SingleTouchInput,
  2258. Recognizer: Recognizer,
  2259. AttrRecognizer: AttrRecognizer,
  2260. Tap: TapRecognizer,
  2261. Pan: PanRecognizer,
  2262. Swipe: SwipeRecognizer,
  2263. Pinch: PinchRecognizer,
  2264. Rotate: RotateRecognizer,
  2265. Press: PressRecognizer,
  2266. on: addEventListeners,
  2267. off: removeEventListeners,
  2268. each: each,
  2269. merge: merge,
  2270. extend: extend,
  2271. assign: assign,
  2272. inherit: inherit,
  2273. bindFn: bindFn,
  2274. prefixed: prefixed
  2275. });
  2276. // this prevents errors when Hammer is loaded in the presence of an AMD
  2277. // style loader but by script tag, not by the loader.
  2278. var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
  2279. freeGlobal.Hammer = Hammer;
  2280. if (typeof define === 'function' && define.amd) {
  2281. define(function() {
  2282. return Hammer;
  2283. });
  2284. } else if (typeof module != 'undefined' && module.exports) {
  2285. module.exports = Hammer;
  2286. } else {
  2287. window[exportName] = Hammer;
  2288. }
  2289. })(window, document, 'Hammer');