1 | ;(function($) {
|
2 | 'use strict';
|
3 | var undefined;
|
4 | var slice = Array.prototype.slice;
|
5 | var isFunction = $.isFunction;
|
6 | var isString = function(obj) {
|
7 | return typeof obj === 'string';
|
8 | };
|
9 | var isObject = function(obj) {
|
10 | return typeof obj === 'object';
|
11 | };
|
12 | var returnFalse = function() {
|
13 | return false;
|
14 | };
|
15 |
|
16 | function calculateAngle(x1, x2, y1, y2) {
|
17 | var x = x1 - x2;
|
18 | var y = y1 - y2;
|
19 | var r = Math.atan2(y, x);
|
20 | var angle = Math.round(r * 180 / Math.PI);
|
21 | if (angle < 0) {
|
22 | angle = 360 - Math.abs(angle);
|
23 | }
|
24 | return angle;
|
25 | }
|
26 |
|
27 | function calculateDirection(x1, x2, y1, y2) {
|
28 | var angle = calculateAngle(x1, x2, y1, y2);
|
29 | if ((angle <= 45 && angle >= 0) || (angle <= 360 && angle >= 315)) {
|
30 | return 'left';
|
31 | } else if (angle >= 135 && angle <= 225) {
|
32 | return 'right';
|
33 | } else if (angle > 45 && angle < 135) {
|
34 | return 'down';
|
35 | } else {
|
36 | return 'up';
|
37 | }
|
38 | }
|
39 |
|
40 | var PLUGIN_NS = '_TOUCH_';
|
41 | var PLUGIN_EVENT_NS = '.' + PLUGIN_NS + 'EVENT_';
|
42 | var SUPPORTS_TOUCH = 'ontouchstart' in window;
|
43 | var SUPPORTS_POINTER_IE10 = window.navigator.msPointerEnabled && !window.navigator.pointerEnabled;
|
44 | var SUPPORTS_POINTER = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
|
45 | var useTouchEvents = SUPPORTS_TOUCH || SUPPORTS_POINTER;
|
46 | var START_EV = (useTouchEvents ? (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerDown' : 'pointerdown') : 'touchstart') : 'mousedown') + PLUGIN_EVENT_NS;
|
47 | var MOVE_EV = (useTouchEvents ? (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerMove' : 'pointermove') : 'touchmove') : 'mousemove') + PLUGIN_EVENT_NS;
|
48 | var END_EV = (useTouchEvents ? (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerUp' : 'pointerup') : 'touchend') : 'mouseup') + PLUGIN_EVENT_NS;
|
49 | var CANCEL_EV = (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerCancel' : 'pointercancel') : 'touchcancel') + PLUGIN_EVENT_NS;
|
50 |
|
51 | var defaults = {
|
52 | fingers: 1,
|
53 | threshold: 75,
|
54 | longTapThreshold: 500,
|
55 | doubleTapThreshold: 200,
|
56 | excludedElements: 'label, button, input, select, textarea, .noTouch',
|
57 | pageScroll: true,
|
58 | swipeMove: null
|
59 | };
|
60 |
|
61 | var _cache = {};
|
62 | $.fn.touch = function(options) {
|
63 | var opts = {};
|
64 | var cid;
|
65 | if (isObject(options)) {
|
66 | $.extend(opts, $.fn.touch.defaults, options);
|
67 | this.each(function () {
|
68 | cid = _tid;
|
69 | _cache[cid] = opts;
|
70 | this._cid = cid;
|
71 | _tid++;
|
72 | });
|
73 | }
|
74 | return this;
|
75 | };
|
76 |
|
77 | $.fn.touch.defaults = defaults;
|
78 |
|
79 | var singleTapTimeout = null;
|
80 | var holdTimeout = null;
|
81 |
|
82 | var _tid = 1;
|
83 | var handlers = {};
|
84 |
|
85 | function isTouchEvent(event) {
|
86 | return /^(tap|doubleTap|longTap|swipe|swipeLeft|swipeRight|swipeUp|swipeDown)$/.test(parse(event).e);
|
87 | }
|
88 |
|
89 | function tid(element) {
|
90 | return element._tid || (element._tid = _tid++);
|
91 | }
|
92 |
|
93 | function parse(event) {
|
94 | var parts = ('' + event).split('.')
|
95 | return {
|
96 | e: parts[0],
|
97 | ns: parts.slice(1).sort().join(' ')
|
98 | }
|
99 | }
|
100 |
|
101 | function matcherFor(ns) {
|
102 | return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)');
|
103 | }
|
104 |
|
105 | function removeTouch(element, event, selector, callback) {
|
106 | var set = handlers[tid(element)];
|
107 | var len = set.length;
|
108 | if (!(Array.isArray(set) && len)) {
|
109 | return;
|
110 | }
|
111 |
|
112 | if (!isString(selector) && !isFunction(callback) && callback !== false) {
|
113 | callback = selector;
|
114 | selector = undefined;
|
115 | }
|
116 | if (callback === false) {
|
117 | callback = returnFalse;
|
118 | }
|
119 |
|
120 | event = parse(event);
|
121 | if (event.ns) {
|
122 | var matcher = matcherFor(event.ns);
|
123 | }
|
124 |
|
125 | var i = 0;
|
126 | for (i; i < len;) {
|
127 | var handler = set[i];
|
128 | if (handler && handler.e === event.e && (!event.ns || matcher.test(handler.ns)) && (!callback || tid(handler.fn) === tid(callback)) && (!selector || handler.sel == selector)) {
|
129 | set.splice(i, 1);
|
130 | len--;
|
131 | } else {
|
132 | i++;
|
133 | }
|
134 | }
|
135 | }
|
136 |
|
137 | function Touch(element, event, selector, data, callback) {
|
138 | if (!isString(selector) && !isFunction(callback) && callback !== false) {
|
139 | callback = data;
|
140 | data = selector;
|
141 | selector = undefined;
|
142 | }
|
143 | if (callback === undefined || data === false) {
|
144 | callback = data;
|
145 | data = undefined;
|
146 | }
|
147 | if (callback === false) {
|
148 | callback = returnFalse;
|
149 | }
|
150 |
|
151 | var handler = parse(event);
|
152 | handler.fn = callback;
|
153 | handler.callback = callback;
|
154 |
|
155 | if (selector) {
|
156 | handler.callback = function(e) {
|
157 | var match = $(e.target).closest(selector, element).get(0);
|
158 | if (match && match !== element) {
|
159 | return callback.apply(match, arguments);
|
160 | }
|
161 | };
|
162 | handler.sel = selector;
|
163 | }
|
164 |
|
165 | var hasTouch = !!element._tid;
|
166 |
|
167 | var id = tid(element);
|
168 | var set = (handlers[id] || (handlers[id] = []));
|
169 | set.push(handler);
|
170 |
|
171 | if (isObject(Touch.instance) && hasTouch) {
|
172 | return Touch.instance;
|
173 | }
|
174 |
|
175 | this.handler = set;
|
176 | this.el = element;
|
177 | this.$el = $(element);
|
178 | this.options = _cache[element._cid] || $.fn.touch.defaults;
|
179 | this.$el.on(START_EV, $.proxy(this.touchStart, this)).on(CANCEL_EV, $.proxy(this.touchCancel, this));
|
180 |
|
181 | Touch.instance = this;
|
182 | }
|
183 |
|
184 | Touch.prototype = {
|
185 | _isTouch: false,
|
186 | _doubleTapTime: 0,
|
187 | touch: {},
|
188 | touchStart: function(e) {
|
189 | var _this = this;
|
190 | var options = this.options;
|
191 | if (this._isTouch || !this.handler.length || $(e.target).closest(options.excludedElements, this.el).length) {
|
192 | return;
|
193 | }
|
194 |
|
195 | this._status = 'start';
|
196 |
|
197 | var touches = e.touches;
|
198 | var evt = touches ? touches[0] : e;
|
199 | var fingerCount = 0;
|
200 |
|
201 | if (touches) {
|
202 | fingerCount = touches.length;
|
203 | }
|
204 |
|
205 | if (!options.pageScroll || !touches) {
|
206 | e.preventDefault();
|
207 | }
|
208 |
|
209 | this.createTouchData(evt);
|
210 |
|
211 | if (!touches || (fingerCount === options.fingers || options.fingers === 'all')) {
|
212 | if (this.hasEvent('longTap')) {
|
213 | holdTimeout = setTimeout(function() {
|
214 | _this.trigger('longTap', e);
|
215 | }, options.longTapThreshold);
|
216 | }
|
217 | this.setTouchProgress(true);
|
218 | } else {
|
219 | this._status = 'cancel';
|
220 | this.triggerHandler(e);
|
221 | return false;
|
222 | }
|
223 | },
|
224 | touchMove: function(e) {
|
225 | if (this._status === 'end' || this._status === 'cancel') {
|
226 | return;
|
227 | }
|
228 | if (this.hasEvent('longTap')) {
|
229 | holdTimeout && clearTimeout(holdTimeout);
|
230 | holdTimeout = null;
|
231 | return;
|
232 | }
|
233 | if (!this.options.pageScroll) {
|
234 | e.preventDefault();
|
235 | }
|
236 | var touches = e.touches;
|
237 | var evt = touches ? touches[0] : e;
|
238 | this.updateTouchData(evt);
|
239 | this.touch.now = e.timeStamp;
|
240 | if (this.hasSwipe()) {
|
241 | this._status = 'move';
|
242 | if (this.isSwipe()) {
|
243 | e.preventDefault();
|
244 | }
|
245 | if (isFunction(this.options.swipeMove)) {
|
246 | var direction = this.getDirection();
|
247 | var distance = Math.abs((direction === 'up' || direction === 'down') ? this.touch.y2 - this.touch.y1 : this.touch.x2 - this.touch.x1);
|
248 | var duration = this.getDuration();
|
249 | this.$el.trigger('swipeMove', [direction, distance, duration, e]);
|
250 | this.options.swipeMove.call(this.el, e, direction, distance, duration);
|
251 | }
|
252 | } else {
|
253 | this._status = 'cancel';
|
254 | this.triggerHandler(e);
|
255 | }
|
256 | },
|
257 | touchEnd: function(e) {
|
258 | if (e.touches && e.touches.length) {
|
259 | return true;
|
260 | }
|
261 | this.touch.now = e.timeStamp;
|
262 | this._status = this._status === 'move' ? 'end' : 'cancel';
|
263 |
|
264 | this.triggerHandler(e);
|
265 |
|
266 | this.touchCancel();
|
267 | },
|
268 | touchCancel: function() {
|
269 | this.touch = {};
|
270 | this.setTouchProgress(false);
|
271 | },
|
272 | triggerHandler: function(e) {
|
273 | var _this = this;
|
274 | if (this._status === 'end' && this.isSwipe() && this.hasSwipe()) {
|
275 | e.preventDefault();
|
276 | var direction = this.getDirection();
|
277 | this.trigger($.camelCase('swipe-' + direction), e);
|
278 | if (this.hasEvent('swipe')) {
|
279 | this.trigger('swipe', e, direction);
|
280 | }
|
281 | } else if (this._status === 'cancel' || this._status === 'end') {
|
282 | holdTimeout && clearTimeout(holdTimeout);
|
283 | singleTapTimeout && clearTimeout(singleTapTimeout);
|
284 | holdTimeout = singleTapTimeout = null;
|
285 |
|
286 | if (this.isDoubleTap()) {
|
287 | this._doubleTapTime = null;
|
288 | this.trigger('doubleTap', e);
|
289 | } else if (this.isLongTap()) {
|
290 | this._doubleTapTime = null;
|
291 | this.trigger('longTap', e);
|
292 | } else if (this.isTap()) {
|
293 | if (this.hasEvent('doubleTap') && !this.isDoubleTap()) {
|
294 | this._doubleTapTime = this.touch.now;
|
295 | singleTapTimeout = setTimeout(function() {
|
296 | _this._doubleTapTime = null;
|
297 | }, this.options.doubleTapThreshold);
|
298 | } else {
|
299 | this._doubleTapTime = null;
|
300 | }
|
301 | if (this.hasEvent('tap')) {
|
302 | this.trigger('tap', e);
|
303 | }
|
304 | }
|
305 | this.touchCancel();
|
306 | }
|
307 | },
|
308 | trigger: function(event, evt) {
|
309 | var _this = this;
|
310 | var args = slice.call(arguments, 1);
|
311 | this.$el.trigger(event, evt);
|
312 | this.handler.forEach(function (handler) {
|
313 | handler && handler.e === event && handler.callback.apply(_this.el, args);
|
314 | });
|
315 | },
|
316 | hasEvent: function(event) {
|
317 | var handler = this.handler;
|
318 | var ret = false;
|
319 | var reg = new RegExp('^(' + event + ')$');
|
320 | for (var i = handler.length - 1; i >= 0; i--) {
|
321 | if (reg.test(handler[i].e)) {
|
322 | ret = true;
|
323 | break;
|
324 | }
|
325 | }
|
326 | return ret;
|
327 | },
|
328 | hasSwipe: function() {
|
329 | return this.hasEvent('swipe|swipeLeft|swipeRight|swipeUp|swipeDown');
|
330 | },
|
331 | isSwipe: function() {
|
332 | return this.getDistance() >= this.options.threshold;
|
333 | },
|
334 | isDoubleTap: function() {
|
335 | if (this._doubleTapTime === null) {
|
336 | return false;
|
337 | }
|
338 | return this.hasEvent('doubleTap') && ((this.touch.now - this._doubleTapTime) <= this.options.doubleTapThreshold);
|
339 | },
|
340 | hasTap: function() {
|
341 | return this.hasEvent('tap|doubleTap');
|
342 | },
|
343 | isTap: function() {
|
344 | return this.hasTap() && this.getDistance() < this.options.threshold;
|
345 | },
|
346 | isLongTap: function() {
|
347 | return this.hasEvent('longTap') && this.getDuration() > this.options.longTapThreshold && this.getDistance() < 10;
|
348 | },
|
349 | createTouchData: function(e) {
|
350 | var touch = {};
|
351 | touch.x1 = touch.x2 = e.pageX || e.clientX;
|
352 | touch.y1 = touch.y2 = e.pageY || e.clientY;
|
353 | touch.now = touch.last = new Date().getTime();
|
354 | this.touch = touch;
|
355 | return touch;
|
356 | },
|
357 | updateTouchData: function(e) {
|
358 | this.touch.x2 = e.pageX || e.clientX;
|
359 | this.touch.y2 = e.pageY || e.clientY;
|
360 | },
|
361 | getDirection: function() {
|
362 | var touch = this.touch;
|
363 | return calculateDirection(touch.x1, touch.x2, touch.y1, touch.y2);
|
364 | },
|
365 | getDistance: function() {
|
366 | var touch = this.touch;
|
367 | return Math.round(Math.sqrt(Math.pow(touch.x2 - touch.x1, 2) + Math.pow(touch.y2 - touch.y1, 2)));
|
368 | },
|
369 | getDuration: function() {
|
370 | var touch = this.touch;
|
371 | return touch.now - touch.last;
|
372 | },
|
373 | setTouchProgress: function(isTouch) {
|
374 | if (isTouch) {
|
375 | this.$el.on(MOVE_EV, $.proxy(this.touchMove, this)).on(END_EV, $.proxy(this.touchEnd, this));
|
376 | } else {
|
377 | this.$el.off(MOVE_EV, $.proxy(this.touchMove, this)).off(END_EV, $.proxy(this.touchEnd, this));
|
378 | }
|
379 | this._isTouch = isTouch;
|
380 | }
|
381 | };
|
382 |
|
383 | var _on = $.fn.on;
|
384 | var _off = $.fn.off;
|
385 |
|
386 | $.fn.on = function(event, selector, data, callback) {
|
387 | if (event && !isString(event)) {
|
388 | return _on.apply(this, arguments);
|
389 | }
|
390 |
|
391 | if (isTouchEvent(event)) {
|
392 | this.each(function() {
|
393 | new Touch(this, event, selector, data, callback);
|
394 | });
|
395 | return this;
|
396 | } else {
|
397 | return _on.apply(this, arguments);
|
398 | }
|
399 | };
|
400 |
|
401 | $.fn.off = function(event, selector, callback) {
|
402 | if (event && !isString(event)) {
|
403 | return _off.apply(this, arguments);
|
404 | }
|
405 |
|
406 | if (isTouchEvent(event)) {
|
407 | this.each(function() {
|
408 | removeTouch(this, event, selector, callback);
|
409 | });
|
410 | return this;
|
411 | } else {
|
412 | if (!event) {
|
413 | this.each(function() {
|
414 | handlers[tid(this)] = [];
|
415 | });
|
416 | }
|
417 | return _off.apply(this, arguments);
|
418 | }
|
419 | };
|
420 |
|
421 | })(window.Zepto || window.jQuery);
|