input.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
  2. var SUPPORT_TOUCH = ('ontouchstart' in window);
  3. var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
  4. var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
  5. var INPUT_TYPE_TOUCH = 'touch';
  6. var INPUT_TYPE_PEN = 'pen';
  7. var INPUT_TYPE_MOUSE = 'mouse';
  8. var INPUT_TYPE_KINECT = 'kinect';
  9. var COMPUTE_INTERVAL = 25;
  10. var INPUT_START = 1;
  11. var INPUT_MOVE = 2;
  12. var INPUT_END = 4;
  13. var INPUT_CANCEL = 8;
  14. var DIRECTION_NONE = 1;
  15. var DIRECTION_LEFT = 2;
  16. var DIRECTION_RIGHT = 4;
  17. var DIRECTION_UP = 8;
  18. var DIRECTION_DOWN = 16;
  19. var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
  20. var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
  21. var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
  22. var PROPS_XY = ['x', 'y'];
  23. var PROPS_CLIENT_XY = ['clientX', 'clientY'];
  24. /**
  25. * create new input type manager
  26. * @param {Manager} manager
  27. * @param {Function} callback
  28. * @returns {Input}
  29. * @constructor
  30. */
  31. function Input(manager, callback) {
  32. var self = this;
  33. this.manager = manager;
  34. this.callback = callback;
  35. this.element = manager.element;
  36. this.target = manager.options.inputTarget;
  37. // smaller wrapper around the handler, for the scope and the enabled state of the manager,
  38. // so when disabled the input events are completely bypassed.
  39. this.domHandler = function(ev) {
  40. if (boolOrFn(manager.options.enable, [manager])) {
  41. self.handler(ev);
  42. }
  43. };
  44. this.init();
  45. }
  46. Input.prototype = {
  47. /**
  48. * should handle the inputEvent data and trigger the callback
  49. * @virtual
  50. */
  51. handler: function() { },
  52. /**
  53. * bind the events
  54. */
  55. init: function() {
  56. this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
  57. this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
  58. this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
  59. },
  60. /**
  61. * unbind the events
  62. */
  63. destroy: function() {
  64. this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
  65. this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
  66. this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
  67. }
  68. };
  69. /**
  70. * create new input type manager
  71. * called by the Manager constructor
  72. * @param {Hammer} manager
  73. * @returns {Input}
  74. */
  75. function createInputInstance(manager) {
  76. var Type;
  77. var inputClass = manager.options.inputClass;
  78. if (inputClass) {
  79. Type = inputClass;
  80. } else if (SUPPORT_POINTER_EVENTS) {
  81. Type = PointerEventInput;
  82. } else if (SUPPORT_ONLY_TOUCH) {
  83. Type = TouchInput;
  84. } else if (!SUPPORT_TOUCH) {
  85. Type = MouseInput;
  86. } else {
  87. Type = TouchMouseInput;
  88. }
  89. return new (Type)(manager, inputHandler);
  90. }
  91. /**
  92. * handle input events
  93. * @param {Manager} manager
  94. * @param {String} eventType
  95. * @param {Object} input
  96. */
  97. function inputHandler(manager, eventType, input) {
  98. var pointersLen = input.pointers.length;
  99. var changedPointersLen = input.changedPointers.length;
  100. var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
  101. var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
  102. input.isFirst = !!isFirst;
  103. input.isFinal = !!isFinal;
  104. if (isFirst) {
  105. manager.session = {};
  106. }
  107. // source event is the normalized value of the domEvents
  108. // like 'touchstart, mouseup, pointerdown'
  109. input.eventType = eventType;
  110. // compute scale, rotation etc
  111. computeInputData(manager, input);
  112. // emit secret event
  113. manager.emit('hammer.input', input);
  114. manager.recognize(input);
  115. manager.session.prevInput = input;
  116. }
  117. /**
  118. * extend the data with some usable properties like scale, rotate, velocity etc
  119. * @param {Object} manager
  120. * @param {Object} input
  121. */
  122. function computeInputData(manager, input) {
  123. var session = manager.session;
  124. var pointers = input.pointers;
  125. var pointersLength = pointers.length;
  126. // store the first input to calculate the distance and direction
  127. if (!session.firstInput) {
  128. session.firstInput = simpleCloneInputData(input);
  129. }
  130. // to compute scale and rotation we need to store the multiple touches
  131. if (pointersLength > 1 && !session.firstMultiple) {
  132. session.firstMultiple = simpleCloneInputData(input);
  133. } else if (pointersLength === 1) {
  134. session.firstMultiple = false;
  135. }
  136. var firstInput = session.firstInput;
  137. var firstMultiple = session.firstMultiple;
  138. var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
  139. var center = input.center = getCenter(pointers);
  140. input.timeStamp = now();
  141. input.deltaTime = input.timeStamp - firstInput.timeStamp;
  142. input.angle = getAngle(offsetCenter, center);
  143. input.distance = getDistance(offsetCenter, center);
  144. computeDeltaXY(session, input);
  145. input.offsetDirection = getDirection(input.deltaX, input.deltaY);
  146. var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
  147. input.overallVelocityX = overallVelocity.x;
  148. input.overallVelocityY = overallVelocity.y;
  149. input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
  150. input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
  151. input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
  152. input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
  153. session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
  154. computeIntervalInputData(session, input);
  155. // find the correct target
  156. var target = manager.element;
  157. if (hasParent(input.srcEvent.target, target)) {
  158. target = input.srcEvent.target;
  159. }
  160. input.target = target;
  161. }
  162. function computeDeltaXY(session, input) {
  163. var center = input.center;
  164. var offset = session.offsetDelta || {};
  165. var prevDelta = session.prevDelta || {};
  166. var prevInput = session.prevInput || {};
  167. if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
  168. prevDelta = session.prevDelta = {
  169. x: prevInput.deltaX || 0,
  170. y: prevInput.deltaY || 0
  171. };
  172. offset = session.offsetDelta = {
  173. x: center.x,
  174. y: center.y
  175. };
  176. }
  177. input.deltaX = prevDelta.x + (center.x - offset.x);
  178. input.deltaY = prevDelta.y + (center.y - offset.y);
  179. }
  180. /**
  181. * velocity is calculated every x ms
  182. * @param {Object} session
  183. * @param {Object} input
  184. */
  185. function computeIntervalInputData(session, input) {
  186. var last = session.lastInterval || input,
  187. deltaTime = input.timeStamp - last.timeStamp,
  188. velocity, velocityX, velocityY, direction;
  189. if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
  190. var deltaX = input.deltaX - last.deltaX;
  191. var deltaY = input.deltaY - last.deltaY;
  192. var v = getVelocity(deltaTime, deltaX, deltaY);
  193. velocityX = v.x;
  194. velocityY = v.y;
  195. velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
  196. direction = getDirection(deltaX, deltaY);
  197. session.lastInterval = input;
  198. } else {
  199. // use latest velocity info if it doesn't overtake a minimum period
  200. velocity = last.velocity;
  201. velocityX = last.velocityX;
  202. velocityY = last.velocityY;
  203. direction = last.direction;
  204. }
  205. input.velocity = velocity;
  206. input.velocityX = velocityX;
  207. input.velocityY = velocityY;
  208. input.direction = direction;
  209. }
  210. /**
  211. * create a simple clone from the input used for storage of firstInput and firstMultiple
  212. * @param {Object} input
  213. * @returns {Object} clonedInputData
  214. */
  215. function simpleCloneInputData(input) {
  216. // make a simple copy of the pointers because we will get a reference if we don't
  217. // we only need clientXY for the calculations
  218. var pointers = [];
  219. var i = 0;
  220. while (i < input.pointers.length) {
  221. pointers[i] = {
  222. clientX: round(input.pointers[i].clientX),
  223. clientY: round(input.pointers[i].clientY)
  224. };
  225. i++;
  226. }
  227. return {
  228. timeStamp: now(),
  229. pointers: pointers,
  230. center: getCenter(pointers),
  231. deltaX: input.deltaX,
  232. deltaY: input.deltaY
  233. };
  234. }
  235. /**
  236. * get the center of all the pointers
  237. * @param {Array} pointers
  238. * @return {Object} center contains `x` and `y` properties
  239. */
  240. function getCenter(pointers) {
  241. var pointersLength = pointers.length;
  242. // no need to loop when only one touch
  243. if (pointersLength === 1) {
  244. return {
  245. x: round(pointers[0].clientX),
  246. y: round(pointers[0].clientY)
  247. };
  248. }
  249. var x = 0, y = 0, i = 0;
  250. while (i < pointersLength) {
  251. x += pointers[i].clientX;
  252. y += pointers[i].clientY;
  253. i++;
  254. }
  255. return {
  256. x: round(x / pointersLength),
  257. y: round(y / pointersLength)
  258. };
  259. }
  260. /**
  261. * calculate the velocity between two points. unit is in px per ms.
  262. * @param {Number} deltaTime
  263. * @param {Number} x
  264. * @param {Number} y
  265. * @return {Object} velocity `x` and `y`
  266. */
  267. function getVelocity(deltaTime, x, y) {
  268. return {
  269. x: x / deltaTime || 0,
  270. y: y / deltaTime || 0
  271. };
  272. }
  273. /**
  274. * get the direction between two points
  275. * @param {Number} x
  276. * @param {Number} y
  277. * @return {Number} direction
  278. */
  279. function getDirection(x, y) {
  280. if (x === y) {
  281. return DIRECTION_NONE;
  282. }
  283. if (abs(x) >= abs(y)) {
  284. return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  285. }
  286. return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
  287. }
  288. /**
  289. * calculate the absolute distance between two points
  290. * @param {Object} p1 {x, y}
  291. * @param {Object} p2 {x, y}
  292. * @param {Array} [props] containing x and y keys
  293. * @return {Number} distance
  294. */
  295. function getDistance(p1, p2, props) {
  296. if (!props) {
  297. props = PROPS_XY;
  298. }
  299. var x = p2[props[0]] - p1[props[0]],
  300. y = p2[props[1]] - p1[props[1]];
  301. return Math.sqrt((x * x) + (y * y));
  302. }
  303. /**
  304. * calculate the angle between two coordinates
  305. * @param {Object} p1
  306. * @param {Object} p2
  307. * @param {Array} [props] containing x and y keys
  308. * @return {Number} angle
  309. */
  310. function getAngle(p1, p2, props) {
  311. if (!props) {
  312. props = PROPS_XY;
  313. }
  314. var x = p2[props[0]] - p1[props[0]],
  315. y = p2[props[1]] - p1[props[1]];
  316. return Math.atan2(y, x) * 180 / Math.PI;
  317. }
  318. /**
  319. * calculate the rotation degrees between two pointersets
  320. * @param {Array} start array of pointers
  321. * @param {Array} end array of pointers
  322. * @return {Number} rotation
  323. */
  324. function getRotation(start, end) {
  325. return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
  326. }
  327. /**
  328. * calculate the scale factor between two pointersets
  329. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  330. * @param {Array} start array of pointers
  331. * @param {Array} end array of pointers
  332. * @return {Number} scale
  333. */
  334. function getScale(start, end) {
  335. return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
  336. }