jquery.flot.touch.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. /* global jQuery */
  2. (function($) {
  3. 'use strict';
  4. var options = {
  5. propagateSupportedGesture: false
  6. };
  7. function init(plot) {
  8. plot.hooks.processOptions.push(initTouchNavigation);
  9. }
  10. function initTouchNavigation(plot, options) {
  11. var gestureState = {
  12. twoTouches: false,
  13. currentTapStart: { x: 0, y: 0 },
  14. currentTapEnd: { x: 0, y: 0 },
  15. prevTap: { x: 0, y: 0 },
  16. currentTap: { x: 0, y: 0 },
  17. interceptedLongTap: false,
  18. isUnsupportedGesture: false,
  19. prevTapTime: null,
  20. tapStartTime: null,
  21. longTapTriggerId: null
  22. },
  23. maxDistanceBetweenTaps = 20,
  24. maxIntervalBetweenTaps = 500,
  25. maxLongTapDistance = 20,
  26. minLongTapDuration = 1500,
  27. pressedTapDuration = 125,
  28. mainEventHolder;
  29. function interpretGestures(e) {
  30. var o = plot.getOptions();
  31. if (!o.pan.active && !o.zoom.active) {
  32. return;
  33. }
  34. updateOnMultipleTouches(e);
  35. mainEventHolder.dispatchEvent(new CustomEvent('touchevent', { detail: e }));
  36. if (isPinchEvent(e)) {
  37. executeAction(e, 'pinch');
  38. } else {
  39. executeAction(e, 'pan');
  40. if (!wasPinchEvent(e)) {
  41. if (isDoubleTap(e)) {
  42. executeAction(e, 'doubleTap');
  43. }
  44. executeAction(e, 'tap');
  45. executeAction(e, 'longTap');
  46. }
  47. }
  48. }
  49. function executeAction(e, gesture) {
  50. switch (gesture) {
  51. case 'pan':
  52. pan[e.type](e);
  53. break;
  54. case 'pinch':
  55. pinch[e.type](e);
  56. break;
  57. case 'doubleTap':
  58. doubleTap.onDoubleTap(e);
  59. break;
  60. case 'longTap':
  61. longTap[e.type](e);
  62. break;
  63. case 'tap':
  64. tap[e.type](e);
  65. break;
  66. }
  67. }
  68. function bindEvents(plot, eventHolder) {
  69. mainEventHolder = eventHolder[0];
  70. eventHolder[0].addEventListener('touchstart', interpretGestures, false);
  71. eventHolder[0].addEventListener('touchmove', interpretGestures, false);
  72. eventHolder[0].addEventListener('touchend', interpretGestures, false);
  73. }
  74. function shutdown(plot, eventHolder) {
  75. eventHolder[0].removeEventListener('touchstart', interpretGestures);
  76. eventHolder[0].removeEventListener('touchmove', interpretGestures);
  77. eventHolder[0].removeEventListener('touchend', interpretGestures);
  78. if (gestureState.longTapTriggerId) {
  79. clearTimeout(gestureState.longTapTriggerId);
  80. gestureState.longTapTriggerId = null;
  81. }
  82. }
  83. var pan = {
  84. touchstart: function(e) {
  85. updatePrevForDoubleTap();
  86. updateCurrentForDoubleTap(e);
  87. updateStateForLongTapStart(e);
  88. mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
  89. },
  90. touchmove: function(e) {
  91. preventEventBehaviors(e);
  92. updateCurrentForDoubleTap(e);
  93. updateStateForLongTapEnd(e);
  94. if (!gestureState.isUnsupportedGesture) {
  95. mainEventHolder.dispatchEvent(new CustomEvent('pandrag', { detail: e }));
  96. }
  97. },
  98. touchend: function(e) {
  99. preventEventBehaviors(e);
  100. if (wasPinchEvent(e)) {
  101. mainEventHolder.dispatchEvent(new CustomEvent('pinchend', { detail: e }));
  102. mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
  103. } else if (noTouchActive(e)) {
  104. mainEventHolder.dispatchEvent(new CustomEvent('panend', { detail: e }));
  105. }
  106. }
  107. };
  108. var pinch = {
  109. touchstart: function(e) {
  110. mainEventHolder.dispatchEvent(new CustomEvent('pinchstart', { detail: e }));
  111. },
  112. touchmove: function(e) {
  113. preventEventBehaviors(e);
  114. gestureState.twoTouches = isPinchEvent(e);
  115. if (!gestureState.isUnsupportedGesture) {
  116. mainEventHolder.dispatchEvent(new CustomEvent('pinchdrag', { detail: e }));
  117. }
  118. },
  119. touchend: function(e) {
  120. preventEventBehaviors(e);
  121. }
  122. };
  123. var doubleTap = {
  124. onDoubleTap: function(e) {
  125. preventEventBehaviors(e);
  126. mainEventHolder.dispatchEvent(new CustomEvent('doubletap', { detail: e }));
  127. }
  128. };
  129. var longTap = {
  130. touchstart: function(e) {
  131. longTap.waitForLongTap(e);
  132. },
  133. touchmove: function(e) {
  134. },
  135. touchend: function(e) {
  136. if (gestureState.longTapTriggerId) {
  137. clearTimeout(gestureState.longTapTriggerId);
  138. gestureState.longTapTriggerId = null;
  139. }
  140. },
  141. isLongTap: function(e) {
  142. var currentTime = new Date().getTime(),
  143. tapDuration = currentTime - gestureState.tapStartTime;
  144. if (tapDuration >= minLongTapDuration && !gestureState.interceptedLongTap) {
  145. if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
  146. gestureState.interceptedLongTap = true;
  147. return true;
  148. }
  149. }
  150. return false;
  151. },
  152. waitForLongTap: function(e) {
  153. var longTapTrigger = function() {
  154. if (longTap.isLongTap(e)) {
  155. mainEventHolder.dispatchEvent(new CustomEvent('longtap', { detail: e }));
  156. }
  157. gestureState.longTapTriggerId = null;
  158. };
  159. if (!gestureState.longTapTriggerId) {
  160. gestureState.longTapTriggerId = setTimeout(longTapTrigger, minLongTapDuration);
  161. }
  162. }
  163. };
  164. var tap = {
  165. touchstart: function(e) {
  166. gestureState.tapStartTime = new Date().getTime();
  167. },
  168. touchmove: function(e) {
  169. },
  170. touchend: function(e) {
  171. if (tap.isTap(e)) {
  172. mainEventHolder.dispatchEvent(new CustomEvent('tap', { detail: e }));
  173. preventEventBehaviors(e);
  174. }
  175. },
  176. isTap: function(e) {
  177. var currentTime = new Date().getTime(),
  178. tapDuration = currentTime - gestureState.tapStartTime;
  179. if (tapDuration <= pressedTapDuration) {
  180. if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
  181. return true;
  182. }
  183. }
  184. return false;
  185. }
  186. };
  187. if (options.pan.enableTouch === true || options.zoom.enableTouch) {
  188. plot.hooks.bindEvents.push(bindEvents);
  189. plot.hooks.shutdown.push(shutdown);
  190. };
  191. function updatePrevForDoubleTap() {
  192. gestureState.prevTap = {
  193. x: gestureState.currentTap.x,
  194. y: gestureState.currentTap.y
  195. };
  196. };
  197. function updateCurrentForDoubleTap(e) {
  198. gestureState.currentTap = {
  199. x: e.touches[0].pageX,
  200. y: e.touches[0].pageY
  201. };
  202. }
  203. function updateStateForLongTapStart(e) {
  204. gestureState.tapStartTime = new Date().getTime();
  205. gestureState.interceptedLongTap = false;
  206. gestureState.currentTapStart = {
  207. x: e.touches[0].pageX,
  208. y: e.touches[0].pageY
  209. };
  210. gestureState.currentTapEnd = {
  211. x: e.touches[0].pageX,
  212. y: e.touches[0].pageY
  213. };
  214. };
  215. function updateStateForLongTapEnd(e) {
  216. gestureState.currentTapEnd = {
  217. x: e.touches[0].pageX,
  218. y: e.touches[0].pageY
  219. };
  220. };
  221. function isDoubleTap(e) {
  222. var currentTime = new Date().getTime(),
  223. intervalBetweenTaps = currentTime - gestureState.prevTapTime;
  224. if (intervalBetweenTaps >= 0 && intervalBetweenTaps < maxIntervalBetweenTaps) {
  225. if (distance(gestureState.prevTap.x, gestureState.prevTap.y, gestureState.currentTap.x, gestureState.currentTap.y) < maxDistanceBetweenTaps) {
  226. e.firstTouch = gestureState.prevTap;
  227. e.secondTouch = gestureState.currentTap;
  228. return true;
  229. }
  230. }
  231. gestureState.prevTapTime = currentTime;
  232. return false;
  233. }
  234. function preventEventBehaviors(e) {
  235. if (!gestureState.isUnsupportedGesture) {
  236. e.preventDefault();
  237. if (!plot.getOptions().propagateSupportedGesture) {
  238. e.stopPropagation();
  239. }
  240. }
  241. }
  242. function distance(x1, y1, x2, y2) {
  243. return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
  244. }
  245. function noTouchActive(e) {
  246. return (e.touches && e.touches.length === 0);
  247. }
  248. function wasPinchEvent(e) {
  249. return (gestureState.twoTouches && e.touches.length === 1);
  250. }
  251. function updateOnMultipleTouches(e) {
  252. if (e.touches.length >= 3) {
  253. gestureState.isUnsupportedGesture = true;
  254. } else {
  255. gestureState.isUnsupportedGesture = false;
  256. }
  257. }
  258. function isPinchEvent(e) {
  259. if (e.touches && e.touches.length >= 2) {
  260. if (e.touches[0].target === plot.getEventHolder() &&
  261. e.touches[1].target === plot.getEventHolder()) {
  262. return true;
  263. }
  264. }
  265. return false;
  266. }
  267. }
  268. $.plot.plugins.push({
  269. init: init,
  270. options: options,
  271. name: 'navigateTouch',
  272. version: '0.3'
  273. });
  274. })(jQuery);