UNPKG

45.8 kBJavaScriptView Raw
1/*! Hammer.JS - v1.0.5 - 2013-04-07
2 * http://eightmedia.github.com/hammer.js
3 *
4 * Copyright (c) 2013 Jorik Tangelder <j.tangelder@gmail.com>;
5 * Licensed under the MIT license */
6
7(function(window, undefined) {
8 'use strict';
9
10/**
11 * Hammer
12 * use this to create instances
13 * @param {HTMLElement} element
14 * @param {Object} options
15 * @returns {Hammer.Instance}
16 * @constructor
17 */
18var Hammer = function(element, options) {
19 return new Hammer.Instance(element, options || {});
20};
21
22// default settings
23Hammer.defaults = {
24 // add styles and attributes to the element to prevent the browser from doing
25 // its native behavior. this doesnt prevent the scrolling, but cancels
26 // the contextmenu, tap highlighting etc
27 // set to false to disable this
28 stop_browser_behavior: {
29 // this also triggers onselectstart=false for IE
30 userSelect: 'none',
31 // this makes the element blocking in IE10 >, you could experiment with the value
32 // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241
33 touchAction: 'none',
34 touchCallout: 'none',
35 contentZooming: 'none',
36 userDrag: 'none',
37 tapHighlightColor: 'rgba(0,0,0,0)'
38 }
39
40 // more settings are defined per gesture at gestures.js
41};
42
43// detect touchevents
44Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
45Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
46
47// dont use mouseevents on mobile devices
48Hammer.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
49Hammer.NO_MOUSEEVENTS = Hammer.HAS_TOUCHEVENTS && navigator.userAgent.match(Hammer.MOBILE_REGEX);
50
51// eventtypes per touchevent (start, move, end)
52// are filled by Hammer.event.determineEventTypes on setup
53Hammer.EVENT_TYPES = {};
54
55// direction defines
56Hammer.DIRECTION_DOWN = 'down';
57Hammer.DIRECTION_LEFT = 'left';
58Hammer.DIRECTION_UP = 'up';
59Hammer.DIRECTION_RIGHT = 'right';
60
61// pointer type
62Hammer.POINTER_MOUSE = 'mouse';
63Hammer.POINTER_TOUCH = 'touch';
64Hammer.POINTER_PEN = 'pen';
65
66// touch event defines
67Hammer.EVENT_START = 'start';
68Hammer.EVENT_MOVE = 'move';
69Hammer.EVENT_END = 'end';
70
71// hammer document where the base events are added at
72Hammer.DOCUMENT = document;
73
74// plugins namespace
75Hammer.plugins = {};
76
77// if the window events are set...
78Hammer.READY = false;
79
80/**
81 * setup events to detect gestures on the document
82 */
83function setup() {
84 if(Hammer.READY) {
85 return;
86 }
87
88 // find what eventtypes we add listeners to
89 Hammer.event.determineEventTypes();
90
91 // Register all gestures inside Hammer.gestures
92 for(var name in Hammer.gestures) {
93 if(Hammer.gestures.hasOwnProperty(name)) {
94 Hammer.detection.register(Hammer.gestures[name]);
95 }
96 }
97
98 // Add touch events on the document
99 Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_MOVE, Hammer.detection.detect);
100 Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_END, Hammer.detection.detect);
101
102 // Hammer is ready...!
103 Hammer.READY = true;
104}
105
106/**
107 * create new hammer instance
108 * all methods should return the instance itself, so it is chainable.
109 * @param {HTMLElement} element
110 * @param {Object} [options={}]
111 * @returns {Hammer.Instance}
112 * @constructor
113 */
114Hammer.Instance = function(element, options) {
115 var self = this;
116
117 // setup HammerJS window events and register all gestures
118 // this also sets up the default options
119 setup();
120
121 this.element = element;
122
123 // start/stop detection option
124 this.enabled = true;
125
126 // merge options
127 this.options = Hammer.utils.extend(
128 Hammer.utils.extend({}, Hammer.defaults),
129 options || {});
130
131 // add some css to the element to prevent the browser from doing its native behavoir
132 if(this.options.stop_browser_behavior) {
133 Hammer.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
134 }
135
136 // start detection on touchstart
137 Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) {
138 if(self.enabled) {
139 Hammer.detection.startDetect(self, ev);
140 }
141 });
142
143 // return instance
144 return this;
145};
146
147
148Hammer.Instance.prototype = {
149 /**
150 * bind events to the instance
151 * @param {String} gesture
152 * @param {Function} handler
153 * @returns {Hammer.Instance}
154 */
155 on: function onEvent(gesture, handler){
156 var gestures = gesture.split(' ');
157 for(var t=0; t<gestures.length; t++) {
158 this.element.addEventListener(gestures[t], handler, false);
159 }
160 return this;
161 },
162
163
164 /**
165 * unbind events to the instance
166 * @param {String} gesture
167 * @param {Function} handler
168 * @returns {Hammer.Instance}
169 */
170 off: function offEvent(gesture, handler){
171 var gestures = gesture.split(' ');
172 for(var t=0; t<gestures.length; t++) {
173 this.element.removeEventListener(gestures[t], handler, false);
174 }
175 return this;
176 },
177
178
179 /**
180 * trigger gesture event
181 * @param {String} gesture
182 * @param {Object} eventData
183 * @returns {Hammer.Instance}
184 */
185 trigger: function triggerEvent(gesture, eventData){
186 // create DOM event
187 var event = Hammer.DOCUMENT.createEvent('Event');
188 event.initEvent(gesture, true, true);
189 event.gesture = eventData;
190
191 // trigger on the target if it is in the instance element,
192 // this is for event delegation tricks
193 var element = this.element;
194 if(Hammer.utils.hasParent(eventData.target, element)) {
195 element = eventData.target;
196 }
197
198 element.dispatchEvent(event);
199 return this;
200 },
201
202
203 /**
204 * enable of disable hammer.js detection
205 * @param {Boolean} state
206 * @returns {Hammer.Instance}
207 */
208 enable: function enable(state) {
209 this.enabled = state;
210 return this;
211 }
212};
213
214/**
215 * this holds the last move event,
216 * used to fix empty touchend issue
217 * see the onTouch event for an explanation
218 * @type {Object}
219 */
220var last_move_event = null;
221
222
223/**
224 * when the mouse is hold down, this is true
225 * @type {Boolean}
226 */
227var enable_detect = false;
228
229
230/**
231 * when touch events have been fired, this is true
232 * @type {Boolean}
233 */
234var touch_triggered = false;
235
236
237Hammer.event = {
238 /**
239 * simple addEventListener
240 * @param {HTMLElement} element
241 * @param {String} type
242 * @param {Function} handler
243 */
244 bindDom: function(element, type, handler) {
245 var types = type.split(' ');
246 for(var t=0; t<types.length; t++) {
247 element.addEventListener(types[t], handler, false);
248 }
249 },
250
251
252 /**
253 * touch events with mouse fallback
254 * @param {HTMLElement} element
255 * @param {String} eventType like Hammer.EVENT_MOVE
256 * @param {Function} handler
257 */
258 onTouch: function onTouch(element, eventType, handler) {
259 var self = this;
260
261 this.bindDom(element, Hammer.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
262 var sourceEventType = ev.type.toLowerCase();
263
264 // onmouseup, but when touchend has been fired we do nothing.
265 // this is for touchdevices which also fire a mouseup on touchend
266 if(sourceEventType.match(/mouse/) && touch_triggered) {
267 return;
268 }
269
270 // mousebutton must be down or a touch event
271 else if( sourceEventType.match(/touch/) || // touch events are always on screen
272 sourceEventType.match(/pointerdown/) || // pointerevents touch
273 (sourceEventType.match(/mouse/) && ev.which === 1) // mouse is pressed
274 ){
275 enable_detect = true;
276 }
277
278 // we are in a touch event, set the touch triggered bool to true,
279 // this for the conflicts that may occur on ios and android
280 if(sourceEventType.match(/touch|pointer/)) {
281 touch_triggered = true;
282 }
283
284 // count the total touches on the screen
285 var count_touches = 0;
286
287 // when touch has been triggered in this detection session
288 // and we are now handling a mouse event, we stop that to prevent conflicts
289 if(enable_detect) {
290 // update pointerevent
291 if(Hammer.HAS_POINTEREVENTS && eventType != Hammer.EVENT_END) {
292 count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
293 }
294 // touch
295 else if(sourceEventType.match(/touch/)) {
296 count_touches = ev.touches.length;
297 }
298 // mouse
299 else if(!touch_triggered) {
300 count_touches = sourceEventType.match(/up/) ? 0 : 1;
301 }
302
303 // if we are in a end event, but when we remove one touch and
304 // we still have enough, set eventType to move
305 if(count_touches > 0 && eventType == Hammer.EVENT_END) {
306 eventType = Hammer.EVENT_MOVE;
307 }
308 // no touches, force the end event
309 else if(!count_touches) {
310 eventType = Hammer.EVENT_END;
311 }
312
313 // because touchend has no touches, and we often want to use these in our gestures,
314 // we send the last move event as our eventData in touchend
315 if(!count_touches && last_move_event !== null) {
316 ev = last_move_event;
317 }
318 // store the last move event
319 else {
320 last_move_event = ev;
321 }
322
323 // trigger the handler
324 handler.call(Hammer.detection, self.collectEventData(element, eventType, ev));
325
326 // remove pointerevent from list
327 if(Hammer.HAS_POINTEREVENTS && eventType == Hammer.EVENT_END) {
328 count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
329 }
330 }
331
332 //debug(sourceEventType +" "+ eventType);
333
334 // on the end we reset everything
335 if(!count_touches) {
336 last_move_event = null;
337 enable_detect = false;
338 touch_triggered = false;
339 Hammer.PointerEvent.reset();
340 }
341 });
342 },
343
344
345 /**
346 * we have different events for each device/browser
347 * determine what we need and set them in the Hammer.EVENT_TYPES constant
348 */
349 determineEventTypes: function determineEventTypes() {
350 // determine the eventtype we want to set
351 var types;
352
353 // pointerEvents magic
354 if(Hammer.HAS_POINTEREVENTS) {
355 types = Hammer.PointerEvent.getEvents();
356 }
357 // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
358 else if(Hammer.NO_MOUSEEVENTS) {
359 types = [
360 'touchstart',
361 'touchmove',
362 'touchend touchcancel'];
363 }
364 // for non pointer events browsers and mixed browsers,
365 // like chrome on windows8 touch laptop
366 else {
367 types = [
368 'touchstart mousedown',
369 'touchmove mousemove',
370 'touchend touchcancel mouseup'];
371 }
372
373 Hammer.EVENT_TYPES[Hammer.EVENT_START] = types[0];
374 Hammer.EVENT_TYPES[Hammer.EVENT_MOVE] = types[1];
375 Hammer.EVENT_TYPES[Hammer.EVENT_END] = types[2];
376 },
377
378
379 /**
380 * create touchlist depending on the event
381 * @param {Object} ev
382 * @param {String} eventType used by the fakemultitouch plugin
383 */
384 getTouchList: function getTouchList(ev/*, eventType*/) {
385 // get the fake pointerEvent touchlist
386 if(Hammer.HAS_POINTEREVENTS) {
387 return Hammer.PointerEvent.getTouchList();
388 }
389 // get the touchlist
390 else if(ev.touches) {
391 return ev.touches;
392 }
393 // make fake touchlist from mouse position
394 else {
395 return [{
396 identifier: 1,
397 pageX: ev.pageX,
398 pageY: ev.pageY,
399 target: ev.target
400 }];
401 }
402 },
403
404
405 /**
406 * collect event data for Hammer js
407 * @param {HTMLElement} element
408 * @param {String} eventType like Hammer.EVENT_MOVE
409 * @param {Object} eventData
410 */
411 collectEventData: function collectEventData(element, eventType, ev) {
412 var touches = this.getTouchList(ev, eventType);
413
414 // find out pointerType
415 var pointerType = Hammer.POINTER_TOUCH;
416 if(ev.type.match(/mouse/) || Hammer.PointerEvent.matchType(Hammer.POINTER_MOUSE, ev)) {
417 pointerType = Hammer.POINTER_MOUSE;
418 }
419
420 return {
421 center : Hammer.utils.getCenter(touches),
422 timeStamp : new Date().getTime(),
423 target : ev.target,
424 touches : touches,
425 eventType : eventType,
426 pointerType : pointerType,
427 srcEvent : ev,
428
429 /**
430 * prevent the browser default actions
431 * mostly used to disable scrolling of the browser
432 */
433 preventDefault: function() {
434 if(this.srcEvent.preventManipulation) {
435 this.srcEvent.preventManipulation();
436 }
437
438 if(this.srcEvent.preventDefault) {
439 this.srcEvent.preventDefault();
440 }
441 },
442
443 /**
444 * stop bubbling the event up to its parents
445 */
446 stopPropagation: function() {
447 this.srcEvent.stopPropagation();
448 },
449
450 /**
451 * immediately stop gesture detection
452 * might be useful after a swipe was detected
453 * @return {*}
454 */
455 stopDetect: function() {
456 return Hammer.detection.stopDetect();
457 }
458 };
459 }
460};
461
462Hammer.PointerEvent = {
463 /**
464 * holds all pointers
465 * @type {Object}
466 */
467 pointers: {},
468
469 /**
470 * get a list of pointers
471 * @returns {Array} touchlist
472 */
473 getTouchList: function() {
474 var self = this;
475 var touchlist = [];
476
477 // we can use forEach since pointerEvents only is in IE10
478 Object.keys(self.pointers).sort().forEach(function(id) {
479 touchlist.push(self.pointers[id]);
480 });
481 return touchlist;
482 },
483
484 /**
485 * update the position of a pointer
486 * @param {String} type Hammer.EVENT_END
487 * @param {Object} pointerEvent
488 */
489 updatePointer: function(type, pointerEvent) {
490 if(type == Hammer.EVENT_END) {
491 this.pointers = {};
492 }
493 else {
494 pointerEvent.identifier = pointerEvent.pointerId;
495 this.pointers[pointerEvent.pointerId] = pointerEvent;
496 }
497
498 return Object.keys(this.pointers).length;
499 },
500
501 /**
502 * check if ev matches pointertype
503 * @param {String} pointerType Hammer.POINTER_MOUSE
504 * @param {PointerEvent} ev
505 */
506 matchType: function(pointerType, ev) {
507 if(!ev.pointerType) {
508 return false;
509 }
510
511 var types = {};
512 types[Hammer.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == Hammer.POINTER_MOUSE);
513 types[Hammer.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == Hammer.POINTER_TOUCH);
514 types[Hammer.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == Hammer.POINTER_PEN);
515 return types[pointerType];
516 },
517
518
519 /**
520 * get events
521 */
522 getEvents: function() {
523 return [
524 'pointerdown MSPointerDown',
525 'pointermove MSPointerMove',
526 'pointerup pointercancel MSPointerUp MSPointerCancel'
527 ];
528 },
529
530 /**
531 * reset the list
532 */
533 reset: function() {
534 this.pointers = {};
535 }
536};
537
538
539Hammer.utils = {
540 /**
541 * extend method,
542 * also used for cloning when dest is an empty object
543 * @param {Object} dest
544 * @param {Object} src
545 * @parm {Boolean} merge do a merge
546 * @returns {Object} dest
547 */
548 extend: function extend(dest, src, merge) {
549 for (var key in src) {
550 if(dest[key] !== undefined && merge) {
551 continue;
552 }
553 dest[key] = src[key];
554 }
555 return dest;
556 },
557
558
559 /**
560 * find if a node is in the given parent
561 * used for event delegation tricks
562 * @param {HTMLElement} node
563 * @param {HTMLElement} parent
564 * @returns {boolean} has_parent
565 */
566 hasParent: function(node, parent) {
567 while(node){
568 if(node == parent) {
569 return true;
570 }
571 node = node.parentNode;
572 }
573 return false;
574 },
575
576
577 /**
578 * get the center of all the touches
579 * @param {Array} touches
580 * @returns {Object} center
581 */
582 getCenter: function getCenter(touches) {
583 var valuesX = [], valuesY = [];
584
585 for(var t= 0,len=touches.length; t<len; t++) {
586 valuesX.push(touches[t].pageX);
587 valuesY.push(touches[t].pageY);
588 }
589
590 return {
591 pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
592 pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
593 };
594 },
595
596
597 /**
598 * calculate the velocity between two points
599 * @param {Number} delta_time
600 * @param {Number} delta_x
601 * @param {Number} delta_y
602 * @returns {Object} velocity
603 */
604 getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
605 return {
606 x: Math.abs(delta_x / delta_time) || 0,
607 y: Math.abs(delta_y / delta_time) || 0
608 };
609 },
610
611
612 /**
613 * calculate the angle between two coordinates
614 * @param {Touch} touch1
615 * @param {Touch} touch2
616 * @returns {Number} angle
617 */
618 getAngle: function getAngle(touch1, touch2) {
619 var y = touch2.pageY - touch1.pageY,
620 x = touch2.pageX - touch1.pageX;
621 return Math.atan2(y, x) * 180 / Math.PI;
622 },
623
624
625 /**
626 * angle to direction define
627 * @param {Touch} touch1
628 * @param {Touch} touch2
629 * @returns {String} direction constant, like Hammer.DIRECTION_LEFT
630 */
631 getDirection: function getDirection(touch1, touch2) {
632 var x = Math.abs(touch1.pageX - touch2.pageX),
633 y = Math.abs(touch1.pageY - touch2.pageY);
634
635 if(x >= y) {
636 return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
637 }
638 else {
639 return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN;
640 }
641 },
642
643
644 /**
645 * calculate the distance between two touches
646 * @param {Touch} touch1
647 * @param {Touch} touch2
648 * @returns {Number} distance
649 */
650 getDistance: function getDistance(touch1, touch2) {
651 var x = touch2.pageX - touch1.pageX,
652 y = touch2.pageY - touch1.pageY;
653 return Math.sqrt((x*x) + (y*y));
654 },
655
656
657 /**
658 * calculate the scale factor between two touchLists (fingers)
659 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
660 * @param {Array} start
661 * @param {Array} end
662 * @returns {Number} scale
663 */
664 getScale: function getScale(start, end) {
665 // need two fingers...
666 if(start.length >= 2 && end.length >= 2) {
667 return this.getDistance(end[0], end[1]) /
668 this.getDistance(start[0], start[1]);
669 }
670 return 1;
671 },
672
673
674 /**
675 * calculate the rotation degrees between two touchLists (fingers)
676 * @param {Array} start
677 * @param {Array} end
678 * @returns {Number} rotation
679 */
680 getRotation: function getRotation(start, end) {
681 // need two fingers
682 if(start.length >= 2 && end.length >= 2) {
683 return this.getAngle(end[1], end[0]) -
684 this.getAngle(start[1], start[0]);
685 }
686 return 0;
687 },
688
689
690 /**
691 * boolean if the direction is vertical
692 * @param {String} direction
693 * @returns {Boolean} is_vertical
694 */
695 isVertical: function isVertical(direction) {
696 return (direction == Hammer.DIRECTION_UP || direction == Hammer.DIRECTION_DOWN);
697 },
698
699
700 /**
701 * stop browser default behavior with css props
702 * @param {HtmlElement} element
703 * @param {Object} css_props
704 */
705 stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) {
706 var prop,
707 vendors = ['webkit','khtml','moz','ms','o',''];
708
709 if(!css_props || !element.style) {
710 return;
711 }
712
713 // with css properties for modern browsers
714 for(var i = 0; i < vendors.length; i++) {
715 for(var p in css_props) {
716 if(css_props.hasOwnProperty(p)) {
717 prop = p;
718
719 // vender prefix at the property
720 if(vendors[i]) {
721 prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1);
722 }
723
724 // set the style
725 element.style[prop] = css_props[p];
726 }
727 }
728 }
729
730 // also the disable onselectstart
731 if(css_props.userSelect == 'none') {
732 element.onselectstart = function() {
733 return false;
734 };
735 }
736 }
737};
738
739Hammer.detection = {
740 // contains all registred Hammer.gestures in the correct order
741 gestures: [],
742
743 // data of the current Hammer.gesture detection session
744 current: null,
745
746 // the previous Hammer.gesture session data
747 // is a full clone of the previous gesture.current object
748 previous: null,
749
750 // when this becomes true, no gestures are fired
751 stopped: false,
752
753
754 /**
755 * start Hammer.gesture detection
756 * @param {Hammer.Instance} inst
757 * @param {Object} eventData
758 */
759 startDetect: function startDetect(inst, eventData) {
760 // already busy with a Hammer.gesture detection on an element
761 if(this.current) {
762 return;
763 }
764
765 this.stopped = false;
766
767 this.current = {
768 inst : inst, // reference to HammerInstance we're working for
769 startEvent : Hammer.utils.extend({}, eventData), // start eventData for distances, timing etc
770 lastEvent : false, // last eventData
771 name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc
772 };
773
774 this.detect(eventData);
775 },
776
777
778 /**
779 * Hammer.gesture detection
780 * @param {Object} eventData
781 * @param {Object} eventData
782 */
783 detect: function detect(eventData) {
784 if(!this.current || this.stopped) {
785 return;
786 }
787
788 // extend event data with calculations about scale, distance etc
789 eventData = this.extendEventData(eventData);
790
791 // instance options
792 var inst_options = this.current.inst.options;
793
794 // call Hammer.gesture handlers
795 for(var g=0,len=this.gestures.length; g<len; g++) {
796 var gesture = this.gestures[g];
797
798 // only when the instance options have enabled this gesture
799 if(!this.stopped && inst_options[gesture.name] !== false) {
800 // if a handler returns false, we stop with the detection
801 if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
802 this.stopDetect();
803 break;
804 }
805 }
806 }
807
808 // store as previous event event
809 if(this.current) {
810 this.current.lastEvent = eventData;
811 }
812
813 // endevent, but not the last touch, so dont stop
814 if(eventData.eventType == Hammer.EVENT_END && !eventData.touches.length-1) {
815 this.stopDetect();
816 }
817
818 return eventData;
819 },
820
821
822 /**
823 * clear the Hammer.gesture vars
824 * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
825 * to stop other Hammer.gestures from being fired
826 */
827 stopDetect: function stopDetect() {
828 // clone current data to the store as the previous gesture
829 // used for the double tap gesture, since this is an other gesture detect session
830 this.previous = Hammer.utils.extend({}, this.current);
831
832 // reset the current
833 this.current = null;
834
835 // stopped!
836 this.stopped = true;
837 },
838
839
840 /**
841 * extend eventData for Hammer.gestures
842 * @param {Object} ev
843 * @returns {Object} ev
844 */
845 extendEventData: function extendEventData(ev) {
846 var startEv = this.current.startEvent;
847
848 // if the touches change, set the new touches over the startEvent touches
849 // this because touchevents don't have all the touches on touchstart, or the
850 // user must place his fingers at the EXACT same time on the screen, which is not realistic
851 // but, sometimes it happens that both fingers are touching at the EXACT same time
852 if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
853 // extend 1 level deep to get the touchlist with the touch objects
854 startEv.touches = [];
855 for(var i=0,len=ev.touches.length; i<len; i++) {
856 startEv.touches.push(Hammer.utils.extend({}, ev.touches[i]));
857 }
858 }
859
860 var delta_time = ev.timeStamp - startEv.timeStamp,
861 delta_x = ev.center.pageX - startEv.center.pageX,
862 delta_y = ev.center.pageY - startEv.center.pageY,
863 velocity = Hammer.utils.getVelocity(delta_time, delta_x, delta_y);
864
865 Hammer.utils.extend(ev, {
866 deltaTime : delta_time,
867
868 deltaX : delta_x,
869 deltaY : delta_y,
870
871 velocityX : velocity.x,
872 velocityY : velocity.y,
873
874 distance : Hammer.utils.getDistance(startEv.center, ev.center),
875 angle : Hammer.utils.getAngle(startEv.center, ev.center),
876 direction : Hammer.utils.getDirection(startEv.center, ev.center),
877
878 scale : Hammer.utils.getScale(startEv.touches, ev.touches),
879 rotation : Hammer.utils.getRotation(startEv.touches, ev.touches),
880
881 startEvent : startEv
882 });
883
884 return ev;
885 },
886
887
888 /**
889 * register new gesture
890 * @param {Object} gesture object, see gestures.js for documentation
891 * @returns {Array} gestures
892 */
893 register: function register(gesture) {
894 // add an enable gesture options if there is no given
895 var options = gesture.defaults || {};
896 if(options[gesture.name] === undefined) {
897 options[gesture.name] = true;
898 }
899
900 // extend Hammer default options with the Hammer.gesture options
901 Hammer.utils.extend(Hammer.defaults, options, true);
902
903 // set its index
904 gesture.index = gesture.index || 1000;
905
906 // add Hammer.gesture to the list
907 this.gestures.push(gesture);
908
909 // sort the list by index
910 this.gestures.sort(function(a, b) {
911 if (a.index < b.index) {
912 return -1;
913 }
914 if (a.index > b.index) {
915 return 1;
916 }
917 return 0;
918 });
919
920 return this.gestures;
921 }
922};
923
924
925Hammer.gestures = Hammer.gestures || {};
926
927/**
928 * Custom gestures
929 * ==============================
930 *
931 * Gesture object
932 * --------------------
933 * The object structure of a gesture:
934 *
935 * { name: 'mygesture',
936 * index: 1337,
937 * defaults: {
938 * mygesture_option: true
939 * }
940 * handler: function(type, ev, inst) {
941 * // trigger gesture event
942 * inst.trigger(this.name, ev);
943 * }
944 * }
945
946 * @param {String} name
947 * this should be the name of the gesture, lowercase
948 * it is also being used to disable/enable the gesture per instance config.
949 *
950 * @param {Number} [index=1000]
951 * the index of the gesture, where it is going to be in the stack of gestures detection
952 * like when you build an gesture that depends on the drag gesture, it is a good
953 * idea to place it after the index of the drag gesture.
954 *
955 * @param {Object} [defaults={}]
956 * the default settings of the gesture. these are added to the instance settings,
957 * and can be overruled per instance. you can also add the name of the gesture,
958 * but this is also added by default (and set to true).
959 *
960 * @param {Function} handler
961 * this handles the gesture detection of your custom gesture and receives the
962 * following arguments:
963 *
964 * @param {Object} eventData
965 * event data containing the following properties:
966 * timeStamp {Number} time the event occurred
967 * target {HTMLElement} target element
968 * touches {Array} touches (fingers, pointers, mouse) on the screen
969 * pointerType {String} kind of pointer that was used. matches Hammer.POINTER_MOUSE|TOUCH
970 * center {Object} center position of the touches. contains pageX and pageY
971 * deltaTime {Number} the total time of the touches in the screen
972 * deltaX {Number} the delta on x axis we haved moved
973 * deltaY {Number} the delta on y axis we haved moved
974 * velocityX {Number} the velocity on the x
975 * velocityY {Number} the velocity on y
976 * angle {Number} the angle we are moving
977 * direction {String} the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT
978 * distance {Number} the distance we haved moved
979 * scale {Number} scaling of the touches, needs 2 touches
980 * rotation {Number} rotation of the touches, needs 2 touches *
981 * eventType {String} matches Hammer.EVENT_START|MOVE|END
982 * srcEvent {Object} the source event, like TouchStart or MouseDown *
983 * startEvent {Object} contains the same properties as above,
984 * but from the first touch. this is used to calculate
985 * distances, deltaTime, scaling etc
986 *
987 * @param {Hammer.Instance} inst
988 * the instance we are doing the detection for. you can get the options from
989 * the inst.options object and trigger the gesture event by calling inst.trigger
990 *
991 *
992 * Handle gestures
993 * --------------------
994 * inside the handler you can get/set Hammer.detection.current. This is the current
995 * detection session. It has the following properties
996 * @param {String} name
997 * contains the name of the gesture we have detected. it has not a real function,
998 * only to check in other gestures if something is detected.
999 * like in the drag gesture we set it to 'drag' and in the swipe gesture we can
1000 * check if the current gesture is 'drag' by accessing Hammer.detection.current.name
1001 *
1002 * @readonly
1003 * @param {Hammer.Instance} inst
1004 * the instance we do the detection for
1005 *
1006 * @readonly
1007 * @param {Object} startEvent
1008 * contains the properties of the first gesture detection in this session.
1009 * Used for calculations about timing, distance, etc.
1010 *
1011 * @readonly
1012 * @param {Object} lastEvent
1013 * contains all the properties of the last gesture detect in this session.
1014 *
1015 * after the gesture detection session has been completed (user has released the screen)
1016 * the Hammer.detection.current object is copied into Hammer.detection.previous,
1017 * this is usefull for gestures like doubletap, where you need to know if the
1018 * previous gesture was a tap
1019 *
1020 * options that have been set by the instance can be received by calling inst.options
1021 *
1022 * You can trigger a gesture event by calling inst.trigger("mygesture", event).
1023 * The first param is the name of your gesture, the second the event argument
1024 *
1025 *
1026 * Register gestures
1027 * --------------------
1028 * When an gesture is added to the Hammer.gestures object, it is auto registered
1029 * at the setup of the first Hammer instance. You can also call Hammer.detection.register
1030 * manually and pass your gesture object as a param
1031 *
1032 */
1033
1034/**
1035 * Hold
1036 * Touch stays at the same place for x time
1037 * @events hold
1038 */
1039Hammer.gestures.Hold = {
1040 name: 'hold',
1041 index: 10,
1042 defaults: {
1043 hold_timeout : 500,
1044 hold_threshold : 1
1045 },
1046 timer: null,
1047 handler: function holdGesture(ev, inst) {
1048 switch(ev.eventType) {
1049 case Hammer.EVENT_START:
1050 // clear any running timers
1051 clearTimeout(this.timer);
1052
1053 // set the gesture so we can check in the timeout if it still is
1054 Hammer.detection.current.name = this.name;
1055
1056 // set timer and if after the timeout it still is hold,
1057 // we trigger the hold event
1058 this.timer = setTimeout(function() {
1059 if(Hammer.detection.current.name == 'hold') {
1060 inst.trigger('hold', ev);
1061 }
1062 }, inst.options.hold_timeout);
1063 break;
1064
1065 // when you move or end we clear the timer
1066 case Hammer.EVENT_MOVE:
1067 if(ev.distance > inst.options.hold_threshold) {
1068 clearTimeout(this.timer);
1069 }
1070 break;
1071
1072 case Hammer.EVENT_END:
1073 clearTimeout(this.timer);
1074 break;
1075 }
1076 }
1077};
1078
1079
1080/**
1081 * Tap/DoubleTap
1082 * Quick touch at a place or double at the same place
1083 * @events tap, doubletap
1084 */
1085Hammer.gestures.Tap = {
1086 name: 'tap',
1087 index: 100,
1088 defaults: {
1089 tap_max_touchtime : 250,
1090 tap_max_distance : 10,
1091 tap_always : true,
1092 doubletap_distance : 20,
1093 doubletap_interval : 300
1094 },
1095 handler: function tapGesture(ev, inst) {
1096 if(ev.eventType == Hammer.EVENT_END) {
1097 // previous gesture, for the double tap since these are two different gesture detections
1098 var prev = Hammer.detection.previous,
1099 did_doubletap = false;
1100
1101 // when the touchtime is higher then the max touch time
1102 // or when the moving distance is too much
1103 if(ev.deltaTime > inst.options.tap_max_touchtime ||
1104 ev.distance > inst.options.tap_max_distance) {
1105 return;
1106 }
1107
1108 // check if double tap
1109 if(prev && prev.name == 'tap' &&
1110 (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
1111 ev.distance < inst.options.doubletap_distance) {
1112 inst.trigger('doubletap', ev);
1113 did_doubletap = true;
1114 }
1115
1116 // do a single tap
1117 if(!did_doubletap || inst.options.tap_always) {
1118 Hammer.detection.current.name = 'tap';
1119 inst.trigger(Hammer.detection.current.name, ev);
1120 }
1121 }
1122 }
1123};
1124
1125
1126/**
1127 * Swipe
1128 * triggers swipe events when the end velocity is above the threshold
1129 * @events swipe, swipeleft, swiperight, swipeup, swipedown
1130 */
1131Hammer.gestures.Swipe = {
1132 name: 'swipe',
1133 index: 40,
1134 defaults: {
1135 // set 0 for unlimited, but this can conflict with transform
1136 swipe_max_touches : 1,
1137 swipe_velocity : 0.7
1138 },
1139 handler: function swipeGesture(ev, inst) {
1140 if(ev.eventType == Hammer.EVENT_END) {
1141 // max touches
1142 if(inst.options.swipe_max_touches > 0 &&
1143 ev.touches.length > inst.options.swipe_max_touches) {
1144 return;
1145 }
1146
1147 // when the distance we moved is too small we skip this gesture
1148 // or we can be already in dragging
1149 if(ev.velocityX > inst.options.swipe_velocity ||
1150 ev.velocityY > inst.options.swipe_velocity) {
1151 // trigger swipe events
1152 inst.trigger(this.name, ev);
1153 inst.trigger(this.name + ev.direction, ev);
1154 }
1155 }
1156 }
1157};
1158
1159
1160/**
1161 * Drag
1162 * Move with x fingers (default 1) around on the page. Blocking the scrolling when
1163 * moving left and right is a good practice. When all the drag events are blocking
1164 * you disable scrolling on that area.
1165 * @events drag, drapleft, dragright, dragup, dragdown
1166 */
1167Hammer.gestures.Drag = {
1168 name: 'drag',
1169 index: 50,
1170 defaults: {
1171 drag_min_distance : 10,
1172 // set 0 for unlimited, but this can conflict with transform
1173 drag_max_touches : 1,
1174 // prevent default browser behavior when dragging occurs
1175 // be careful with it, it makes the element a blocking element
1176 // when you are using the drag gesture, it is a good practice to set this true
1177 drag_block_horizontal : false,
1178 drag_block_vertical : false,
1179 // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
1180 // It disallows vertical directions if the initial direction was horizontal, and vice versa.
1181 drag_lock_to_axis : false,
1182 // drag lock only kicks in when distance > drag_lock_min_distance
1183 // This way, locking occurs only when the distance has become large enough to reliably determine the direction
1184 drag_lock_min_distance : 25
1185 },
1186 triggered: false,
1187 handler: function dragGesture(ev, inst) {
1188 // current gesture isnt drag, but dragged is true
1189 // this means an other gesture is busy. now call dragend
1190 if(Hammer.detection.current.name != this.name && this.triggered) {
1191 inst.trigger(this.name +'end', ev);
1192 this.triggered = false;
1193 return;
1194 }
1195
1196 // max touches
1197 if(inst.options.drag_max_touches > 0 &&
1198 ev.touches.length > inst.options.drag_max_touches) {
1199 return;
1200 }
1201
1202 switch(ev.eventType) {
1203 case Hammer.EVENT_START:
1204 this.triggered = false;
1205 break;
1206
1207 case Hammer.EVENT_MOVE:
1208 // when the distance we moved is too small we skip this gesture
1209 // or we can be already in dragging
1210 if(ev.distance < inst.options.drag_min_distance &&
1211 Hammer.detection.current.name != this.name) {
1212 return;
1213 }
1214
1215 // we are dragging!
1216 Hammer.detection.current.name = this.name;
1217
1218 // lock drag to axis?
1219 if(Hammer.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance<=ev.distance)) {
1220 ev.drag_locked_to_axis = true;
1221 }
1222 var last_direction = Hammer.detection.current.lastEvent.direction;
1223 if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
1224 // keep direction on the axis that the drag gesture started on
1225 if(Hammer.utils.isVertical(last_direction)) {
1226 ev.direction = (ev.deltaY < 0) ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN;
1227 }
1228 else {
1229 ev.direction = (ev.deltaX < 0) ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
1230 }
1231 }
1232
1233 // first time, trigger dragstart event
1234 if(!this.triggered) {
1235 inst.trigger(this.name +'start', ev);
1236 this.triggered = true;
1237 }
1238
1239 // trigger normal event
1240 inst.trigger(this.name, ev);
1241
1242 // direction event, like dragdown
1243 inst.trigger(this.name + ev.direction, ev);
1244
1245 // block the browser events
1246 if( (inst.options.drag_block_vertical && Hammer.utils.isVertical(ev.direction)) ||
1247 (inst.options.drag_block_horizontal && !Hammer.utils.isVertical(ev.direction))) {
1248 ev.preventDefault();
1249 }
1250 break;
1251
1252 case Hammer.EVENT_END:
1253 // trigger dragend
1254 if(this.triggered) {
1255 inst.trigger(this.name +'end', ev);
1256 }
1257
1258 this.triggered = false;
1259 break;
1260 }
1261 }
1262};
1263
1264
1265/**
1266 * Transform
1267 * User want to scale or rotate with 2 fingers
1268 * @events transform, pinch, pinchin, pinchout, rotate
1269 */
1270Hammer.gestures.Transform = {
1271 name: 'transform',
1272 index: 45,
1273 defaults: {
1274 // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
1275 transform_min_scale : 0.01,
1276 // rotation in degrees
1277 transform_min_rotation : 1,
1278 // prevent default browser behavior when two touches are on the screen
1279 // but it makes the element a blocking element
1280 // when you are using the transform gesture, it is a good practice to set this true
1281 transform_always_block : false
1282 },
1283 triggered: false,
1284 handler: function transformGesture(ev, inst) {
1285 // current gesture isnt drag, but dragged is true
1286 // this means an other gesture is busy. now call dragend
1287 if(Hammer.detection.current.name != this.name && this.triggered) {
1288 inst.trigger(this.name +'end', ev);
1289 this.triggered = false;
1290 return;
1291 }
1292
1293 // atleast multitouch
1294 if(ev.touches.length < 2) {
1295 return;
1296 }
1297
1298 // prevent default when two fingers are on the screen
1299 if(inst.options.transform_always_block) {
1300 ev.preventDefault();
1301 }
1302
1303 switch(ev.eventType) {
1304 case Hammer.EVENT_START:
1305 this.triggered = false;
1306 break;
1307
1308 case Hammer.EVENT_MOVE:
1309 var scale_threshold = Math.abs(1-ev.scale);
1310 var rotation_threshold = Math.abs(ev.rotation);
1311
1312 // when the distance we moved is too small we skip this gesture
1313 // or we can be already in dragging
1314 if(scale_threshold < inst.options.transform_min_scale &&
1315 rotation_threshold < inst.options.transform_min_rotation) {
1316 return;
1317 }
1318
1319 // we are transforming!
1320 Hammer.detection.current.name = this.name;
1321
1322 // first time, trigger dragstart event
1323 if(!this.triggered) {
1324 inst.trigger(this.name +'start', ev);
1325 this.triggered = true;
1326 }
1327
1328 inst.trigger(this.name, ev); // basic transform event
1329
1330 // trigger rotate event
1331 if(rotation_threshold > inst.options.transform_min_rotation) {
1332 inst.trigger('rotate', ev);
1333 }
1334
1335 // trigger pinch event
1336 if(scale_threshold > inst.options.transform_min_scale) {
1337 inst.trigger('pinch', ev);
1338 inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev);
1339 }
1340 break;
1341
1342 case Hammer.EVENT_END:
1343 // trigger dragend
1344 if(this.triggered) {
1345 inst.trigger(this.name +'end', ev);
1346 }
1347
1348 this.triggered = false;
1349 break;
1350 }
1351 }
1352};
1353
1354
1355/**
1356 * Touch
1357 * Called as first, tells the user has touched the screen
1358 * @events touch
1359 */
1360Hammer.gestures.Touch = {
1361 name: 'touch',
1362 index: -Infinity,
1363 defaults: {
1364 // call preventDefault at touchstart, and makes the element blocking by
1365 // disabling the scrolling of the page, but it improves gestures like
1366 // transforming and dragging.
1367 // be careful with using this, it can be very annoying for users to be stuck
1368 // on the page
1369 prevent_default: false,
1370
1371 // disable mouse events, so only touch (or pen!) input triggers events
1372 prevent_mouseevents: false
1373 },
1374 handler: function touchGesture(ev, inst) {
1375 if(inst.options.prevent_mouseevents && ev.pointerType == Hammer.POINTER_MOUSE) {
1376 ev.stopDetect();
1377 return;
1378 }
1379
1380 if(inst.options.prevent_default) {
1381 ev.preventDefault();
1382 }
1383
1384 if(ev.eventType == Hammer.EVENT_START) {
1385 inst.trigger(this.name, ev);
1386 }
1387 }
1388};
1389
1390
1391/**
1392 * Release
1393 * Called as last, tells the user has released the screen
1394 * @events release
1395 */
1396Hammer.gestures.Release = {
1397 name: 'release',
1398 index: Infinity,
1399 handler: function releaseGesture(ev, inst) {
1400 if(ev.eventType == Hammer.EVENT_END) {
1401 inst.trigger(this.name, ev);
1402 }
1403 }
1404};
1405
1406// node export
1407if(typeof module === 'object' && typeof module.exports === 'object'){
1408 module.exports = Hammer;
1409}
1410// just window export
1411else {
1412 window.Hammer = Hammer;
1413
1414 // requireJS module definition
1415 if(typeof window.define === 'function' && window.define.amd) {
1416 window.define('hammer', [], function() {
1417 return Hammer;
1418 });
1419 }
1420}
1421})(this);
\No newline at end of file