UNPKG

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