1 /*! Hammer.JS - v1.0.10 - 2014-03-28
2 * http://eightmedia.github.io/hammer.js
4 * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
5 * Licensed under the MIT license */
7 (function(window
, undefined) {
12 * use this to create instances
13 * @param {HTMLElement} element
14 * @param {Object} options
15 * @returns {Hammer.Instance}
18 var Hammer = function(element
, options
) {
19 return new Hammer
.Instance(element
, options
|| {});
22 Hammer
.VERSION
= '1.0.10';
26 // add styles and attributes to the element to prevent the browser from doing
27 // its native behavior. this doesnt prevent the scrolling, but cancels
28 // the contextmenu, tap highlighting etc
29 // set to false to disable this
30 stop_browser_behavior
: {
31 // this also triggers onselectstart=false for IE
33 // this makes the element blocking in IE10>, you could experiment with the value
34 // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241
36 touchCallout
: 'none',
37 contentZooming
: 'none',
39 tapHighlightColor
: 'rgba(0,0,0,0)'
43 // more settings are defined per gesture at /gestures
49 Hammer
.HAS_POINTEREVENTS
= window
.navigator
.pointerEnabled
|| window
.navigator
.msPointerEnabled
;
50 Hammer
.HAS_TOUCHEVENTS
= ('ontouchstart' in window
);
52 // dont use mouseevents on mobile devices
53 Hammer
.MOBILE_REGEX
= /mobile|tablet|ip(ad|hone|od)|android|silk/i;
54 Hammer
.NO_MOUSEEVENTS
= Hammer
.HAS_TOUCHEVENTS
&& window
.navigator
.userAgent
.match(Hammer
.MOBILE_REGEX
);
56 // eventtypes per touchevent (start, move, end)
57 // are filled by Event.determineEventTypes on setup
58 Hammer
.EVENT_TYPES
= {};
60 // interval in which Hammer recalculates current velocity in ms
61 Hammer
.UPDATE_VELOCITY_INTERVAL
= 16;
63 // hammer document where the base events are added at
64 Hammer
.DOCUMENT
= window
.document
;
66 // define these also as vars, for better minification
68 var DIRECTION_DOWN
= Hammer
.DIRECTION_DOWN
= 'down';
69 var DIRECTION_LEFT
= Hammer
.DIRECTION_LEFT
= 'left';
70 var DIRECTION_UP
= Hammer
.DIRECTION_UP
= 'up';
71 var DIRECTION_RIGHT
= Hammer
.DIRECTION_RIGHT
= 'right';
74 var POINTER_MOUSE
= Hammer
.POINTER_MOUSE
= 'mouse';
75 var POINTER_TOUCH
= Hammer
.POINTER_TOUCH
= 'touch';
76 var POINTER_PEN
= Hammer
.POINTER_PEN
= 'pen';
78 // touch event defines
79 var EVENT_START
= Hammer
.EVENT_START
= 'start';
80 var EVENT_MOVE
= Hammer
.EVENT_MOVE
= 'move';
81 var EVENT_END
= Hammer
.EVENT_END
= 'end';
84 // plugins and gestures namespaces
85 Hammer
.plugins
= Hammer
.plugins
|| {};
86 Hammer
.gestures
= Hammer
.gestures
|| {};
89 // if the window events are set...
94 * setup events to detect gestures on the document
101 // find what eventtypes we add listeners to
102 Event
.determineEventTypes();
104 // Register all gestures inside Hammer.gestures
105 Utils
.each(Hammer
.gestures
, function(gesture
){
106 Detection
.register(gesture
);
109 // Add touch events on the document
110 Event
.onTouch(Hammer
.DOCUMENT
, EVENT_MOVE
, Detection
.detect
);
111 Event
.onTouch(Hammer
.DOCUMENT
, EVENT_END
, Detection
.detect
);
113 // Hammer is ready...!
117 var Utils
= Hammer
.utils
= {
120 * also used for cloning when dest is an empty object
121 * @param {Object} dest
122 * @param {Object} src
123 * @parm {Boolean} merge do a merge
124 * @returns {Object} dest
126 extend
: function extend(dest
, src
, merge
) {
127 for(var key
in src
) {
128 if(dest
[key
] !== undefined && merge
) {
131 dest
[key
] = src
[key
];
142 each
: function each(obj
, iterator
, context
) {
144 // native forEach on arrays
145 if ('forEach' in obj
) {
146 obj
.forEach(iterator
, context
);
149 else if(obj
.length
!== undefined) {
150 for(i
=-1; (o
=obj
[++i
]);) {
151 if (iterator
.call(context
, o
, i
, obj
) === false) {
159 if(obj
.hasOwnProperty(i
) &&
160 iterator
.call(context
, obj
[i
], i
, obj
) === false) {
169 * find if a string contains the needle
170 * @param {String} src
171 * @param {String} needle
172 * @returns {Boolean} found
174 inStr
: function inStr(src
, needle
) {
175 return src
.indexOf(needle
) > -1;
180 * find if a node is in the given parent
181 * used for event delegation tricks
182 * @param {HTMLElement} node
183 * @param {HTMLElement} parent
184 * @returns {boolean} has_parent
186 hasParent
: function hasParent(node
, parent
) {
191 node
= node
.parentNode
;
198 * get the center of all the touches
199 * @param {Array} touches
200 * @returns {Object} center pageXY clientXY
202 getCenter
: function getCenter(touches
) {
210 // no need to loop when only one touch
211 if(touches
.length
=== 1) {
213 pageX
: touches
[0].pageX
,
214 pageY
: touches
[0].pageY
,
215 clientX
: touches
[0].clientX
,
216 clientY
: touches
[0].clientY
220 Utils
.each(touches
, function(touch
) {
221 pageX
.push(touch
.pageX
);
222 pageY
.push(touch
.pageY
);
223 clientX
.push(touch
.clientX
);
224 clientY
.push(touch
.clientY
);
228 pageX
: (min
.apply(Math
, pageX
) + max
.apply(Math
, pageX
)) / 2,
229 pageY
: (min
.apply(Math
, pageY
) + max
.apply(Math
, pageY
)) / 2,
230 clientX
: (min
.apply(Math
, clientX
) + max
.apply(Math
, clientX
)) / 2,
231 clientY
: (min
.apply(Math
, clientY
) + max
.apply(Math
, clientY
)) / 2
237 * calculate the velocity between two points
238 * @param {Number} delta_time
239 * @param {Number} delta_x
240 * @param {Number} delta_y
241 * @returns {Object} velocity
243 getVelocity
: function getVelocity(delta_time
, delta_x
, delta_y
) {
245 x
: Math
.abs(delta_x
/ delta_time
) || 0,
246 y
: Math
.abs(delta_y
/ delta_time
) || 0
252 * calculate the angle between two coordinates
253 * @param {Touch} touch1
254 * @param {Touch} touch2
255 * @returns {Number} angle
257 getAngle
: function getAngle(touch1
, touch2
) {
258 var x
= touch2
.clientX
- touch1
.clientX
259 , y
= touch2
.clientY
- touch1
.clientY
;
260 return Math
.atan2(y
, x
) * 180 / Math
.PI
;
265 * angle to direction define
266 * @param {Touch} touch1
267 * @param {Touch} touch2
268 * @returns {String} direction constant, like DIRECTION_LEFT
270 getDirection
: function getDirection(touch1
, touch2
) {
271 var x
= Math
.abs(touch1
.clientX
- touch2
.clientX
)
272 , y
= Math
.abs(touch1
.clientY
- touch2
.clientY
);
274 return touch1
.clientX
- touch2
.clientX
> 0 ? DIRECTION_LEFT
: DIRECTION_RIGHT
;
276 return touch1
.clientY
- touch2
.clientY
> 0 ? DIRECTION_UP
: DIRECTION_DOWN
;
281 * calculate the distance between two touches
282 * @param {Touch} touch1
283 * @param {Touch} touch2
284 * @returns {Number} distance
286 getDistance
: function getDistance(touch1
, touch2
) {
287 var x
= touch2
.clientX
- touch1
.clientX
288 , y
= touch2
.clientY
- touch1
.clientY
;
289 return Math
.sqrt((x
* x
) + (y
* y
));
294 * calculate the scale factor between two touchLists (fingers)
295 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
296 * @param {Array} start
298 * @returns {Number} scale
300 getScale
: function getScale(start
, end
) {
301 // need two fingers...
302 if(start
.length
>= 2 && end
.length
>= 2) {
303 return this.getDistance(end
[0], end
[1]) / this.getDistance(start
[0], start
[1]);
310 * calculate the rotation degrees between two touchLists (fingers)
311 * @param {Array} start
313 * @returns {Number} rotation
315 getRotation
: function getRotation(start
, end
) {
317 if(start
.length
>= 2 && end
.length
>= 2) {
318 return this.getAngle(end
[1], end
[0]) - this.getAngle(start
[1], start
[0]);
325 * boolean if the direction is vertical
326 * @param {String} direction
327 * @returns {Boolean} is_vertical
329 isVertical
: function isVertical(direction
) {
330 return direction
== DIRECTION_UP
|| direction
== DIRECTION_DOWN
;
335 * toggle browser default behavior with css props
336 * @param {HtmlElement} element
337 * @param {Object} css_props
338 * @param {Boolean} toggle
340 toggleDefaultBehavior
: function toggleDefaultBehavior(element
, css_props
, toggle
) {
341 if(!css_props
|| !element
|| !element
.style
) {
345 // with css properties for modern browsers
346 Utils
.each(['webkit', 'moz', 'Moz', 'ms', 'o', ''], function setStyle(vendor
) {
347 Utils
.each(css_props
, function(value
, prop
) {
348 // vender prefix at the property
350 prop
= vendor
+ prop
.substring(0, 1).toUpperCase() + prop
.substring(1);
353 if(prop
in element
.style
) {
354 element
.style
[prop
] = !toggle
&& value
;
359 var false_fn = function(){ return false; };
361 // also the disable onselectstart
362 if(css_props
.userSelect
== 'none') {
363 element
.onselectstart
= !toggle
&& false_fn
;
365 // and disable ondragstart
366 if(css_props
.userDrag
== 'none') {
367 element
.ondragstart
= !toggle
&& false_fn
;
374 * create new hammer instance
375 * all methods should return the instance itself, so it is chainable.
376 * @param {HTMLElement} element
377 * @param {Object} [options={}]
378 * @returns {Hammer.Instance}
381 Hammer
.Instance = function(element
, options
) {
384 // setup HammerJS window events and register all gestures
385 // this also sets up the default options
388 this.element
= element
;
390 // start/stop detection option
394 this.options
= Utils
.extend(
395 Utils
.extend({}, Hammer
.defaults
),
398 // add some css to the element to prevent the browser from doing its native behavoir
399 if(this.options
.stop_browser_behavior
) {
400 Utils
.toggleDefaultBehavior(this.element
, this.options
.stop_browser_behavior
, false);
403 // start detection on touchstart
404 this.eventStartHandler
= Event
.onTouch(element
, EVENT_START
, function(ev
) {
406 Detection
.startDetect(self
, ev
);
410 // keep a list of user event handlers which needs to be removed when calling 'dispose'
411 this.eventHandlers
= [];
418 Hammer
.Instance
.prototype = {
420 * bind events to the instance
421 * @param {String} gesture
422 * @param {Function} handler
423 * @returns {Hammer.Instance}
425 on
: function onEvent(gesture
, handler
) {
426 var gestures
= gesture
.split(' ');
427 Utils
.each(gestures
, function(gesture
) {
428 this.element
.addEventListener(gesture
, handler
, false);
429 this.eventHandlers
.push({ gesture
: gesture
, handler
: handler
});
436 * unbind events to the instance
437 * @param {String} gesture
438 * @param {Function} handler
439 * @returns {Hammer.Instance}
441 off
: function offEvent(gesture
, handler
) {
442 var gestures
= gesture
.split(' ')
444 Utils
.each(gestures
, function(gesture
) {
445 this.element
.removeEventListener(gesture
, handler
, false);
447 // remove the event handler from the internal list
448 for(i
=-1; (eh
=this.eventHandlers
[++i
]);) {
449 if(eh
.gesture
=== gesture
&& eh
.handler
=== handler
) {
450 this.eventHandlers
.splice(i
, 1);
459 * trigger gesture event
460 * @param {String} gesture
461 * @param {Object} [eventData]
462 * @returns {Hammer.Instance}
464 trigger
: function triggerEvent(gesture
, eventData
) {
471 var event
= Hammer
.DOCUMENT
.createEvent('Event');
472 event
.initEvent(gesture
, true, true);
473 event
.gesture
= eventData
;
475 // trigger on the target if it is in the instance element,
476 // this is for event delegation tricks
477 var element
= this.element
;
478 if(Utils
.hasParent(eventData
.target
, element
)) {
479 element
= eventData
.target
;
482 element
.dispatchEvent(event
);
488 * enable of disable hammer.js detection
489 * @param {Boolean} state
490 * @returns {Hammer.Instance}
492 enable
: function enable(state
) {
493 this.enabled
= state
;
499 * dispose this hammer instance
500 * @returns {Hammer.Instance}
502 dispose
: function dispose() {
505 // undo all changes made by stop_browser_behavior
506 if(this.options
.stop_browser_behavior
) {
507 Utils
.toggleDefaultBehavior(this.element
, this.options
.stop_browser_behavior
, true);
510 // unbind all custom event handlers
511 for(i
=-1; (eh
=this.eventHandlers
[++i
]);) {
512 this.element
.removeEventListener(eh
.gesture
, eh
.handler
, false);
514 this.eventHandlers
= [];
516 // unbind the start event listener
517 Event
.unbindDom(this.element
, Hammer
.EVENT_TYPES
[EVENT_START
], this.eventStartHandler
);
525 * this holds the last move event,
526 * used to fix empty touchend issue
527 * see the onTouch event for an explanation
530 var last_move_event
= null;
533 * when the mouse is hold down, this is true
536 var should_detect
= false;
539 * when touch events have been fired, this is true
542 var touch_triggered
= false;
545 var Event
= Hammer
.event
= {
547 * simple addEventListener
548 * @param {HTMLElement} element
549 * @param {String} type
550 * @param {Function} handler
552 bindDom: function(element
, type
, handler
) {
553 var types
= type
.split(' ');
554 Utils
.each(types
, function(type
){
555 element
.addEventListener(type
, handler
, false);
561 * simple removeEventListener
562 * @param {HTMLElement} element
563 * @param {String} type
564 * @param {Function} handler
566 unbindDom: function(element
, type
, handler
) {
567 var types
= type
.split(' ');
568 Utils
.each(types
, function(type
){
569 element
.removeEventListener(type
, handler
, false);
575 * touch events with mouse fallback
576 * @param {HTMLElement} element
577 * @param {String} eventType like EVENT_MOVE
578 * @param {Function} handler
580 onTouch
: function onTouch(element
, eventType
, handler
) {
584 var bindDomOnTouch
= function bindDomOnTouch(ev
) {
585 var srcEventType
= ev
.type
.toLowerCase();
587 // onmouseup, but when touchend has been fired we do nothing.
588 // this is for touchdevices which also fire a mouseup on touchend
589 if(Utils
.inStr(srcEventType
, 'mouse') && touch_triggered
) {
593 // mousebutton must be down or a touch event
594 else if(Utils
.inStr(srcEventType
, 'touch') || // touch events are always on screen
595 Utils
.inStr(srcEventType
, 'pointerdown') || // pointerevents touch
596 (Utils
.inStr(srcEventType
, 'mouse') && ev
.which
=== 1) // mouse is pressed
598 should_detect
= true;
601 // mouse isn't pressed
602 else if(Utils
.inStr(srcEventType
, 'mouse') && !ev
.which
) {
603 should_detect
= false;
607 // we are in a touch event, set the touch triggered bool to true,
608 // this for the conflicts that may occur on ios and android
609 if(Utils
.inStr(srcEventType
, 'touch') || Utils
.inStr(srcEventType
, 'pointer')) {
610 touch_triggered
= true;
613 // count the total touches on the screen
614 var count_touches
= 0;
616 // when touch has been triggered in this detection session
617 // and we are now handling a mouse event, we stop that to prevent conflicts
619 // update pointerevent
620 if(Hammer
.HAS_POINTEREVENTS
&& eventType
!= EVENT_END
) {
621 count_touches
= PointerEvent
.updatePointer(eventType
, ev
);
624 else if(Utils
.inStr(srcEventType
, 'touch')) {
625 count_touches
= ev
.touches
.length
;
628 else if(!touch_triggered
) {
629 count_touches
= Utils
.inStr(srcEventType
, 'up') ? 0 : 1;
633 // if we are in a end event, but when we remove one touch and
634 // we still have enough, set eventType to move
635 if(count_touches
> 0 && eventType
== EVENT_END
) {
636 eventType
= EVENT_MOVE
;
638 // no touches, force the end event
639 else if(!count_touches
) {
640 eventType
= EVENT_END
;
643 // store the last move event
644 if(count_touches
|| last_move_event
=== null) {
645 last_move_event
= ev
;
649 // trigger the handler
650 handler
.call(Detection
, self
.collectEventData(element
, eventType
,
651 self
.getTouchList(last_move_event
, eventType
),
654 // remove pointerevent from list
655 if(Hammer
.HAS_POINTEREVENTS
&& eventType
== EVENT_END
) {
656 count_touches
= PointerEvent
.updatePointer(eventType
, ev
);
660 // on the end we reset everything
662 last_move_event
= null;
663 should_detect
= false;
664 touch_triggered
= false;
665 PointerEvent
.reset();
669 this.bindDom(element
, Hammer
.EVENT_TYPES
[eventType
], bindDomOnTouch
);
671 // return the bound function to be able to unbind it later
672 return bindDomOnTouch
;
677 * we have different events for each device/browser
678 * determine what we need and set them in the Hammer.EVENT_TYPES constant
680 determineEventTypes
: function determineEventTypes() {
681 // determine the eventtype we want to set
684 // pointerEvents magic
685 if(Hammer
.HAS_POINTEREVENTS
) {
686 types
= PointerEvent
.getEvents();
688 // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
689 else if(Hammer
.NO_MOUSEEVENTS
) {
693 'touchend touchcancel'];
695 // for non pointer events browsers and mixed browsers,
696 // like chrome on windows8 touch laptop
699 'touchstart mousedown',
700 'touchmove mousemove',
701 'touchend touchcancel mouseup'];
704 Hammer
.EVENT_TYPES
[EVENT_START
] = types
[0];
705 Hammer
.EVENT_TYPES
[EVENT_MOVE
] = types
[1];
706 Hammer
.EVENT_TYPES
[EVENT_END
] = types
[2];
711 * create touchlist depending on the event
713 * @param {String} eventType used by the fakemultitouch plugin
715 getTouchList
: function getTouchList(ev
/*, eventType*/) {
716 // get the fake pointerEvent touchlist
717 if(Hammer
.HAS_POINTEREVENTS
) {
718 return PointerEvent
.getTouchList();
726 // make fake touchlist from mouse position
733 * collect event data for Hammer js
734 * @param {HTMLElement} element
735 * @param {String} eventType like EVENT_MOVE
736 * @param {Object} eventData
738 collectEventData
: function collectEventData(element
, eventType
, touches
, ev
) {
739 // find out pointerType
740 var pointerType
= POINTER_TOUCH
;
741 if(Utils
.inStr(ev
.type
, 'mouse') || PointerEvent
.matchType(POINTER_MOUSE
, ev
)) {
742 pointerType
= POINTER_MOUSE
;
746 center
: Utils
.getCenter(touches
),
747 timeStamp
: Date
.now(),
750 eventType
: eventType
,
751 pointerType
: pointerType
,
755 * prevent the browser default actions
756 * mostly used to disable scrolling of the browser
758 preventDefault: function() {
759 var srcEvent
= this.srcEvent
;
760 srcEvent
.preventManipulation
&& srcEvent
.preventManipulation();
761 srcEvent
.preventDefault
&& srcEvent
.preventDefault();
765 * stop bubbling the event up to its parents
767 stopPropagation: function() {
768 this.srcEvent
.stopPropagation();
772 * immediately stop gesture detection
773 * might be useful after a swipe was detected
776 stopDetect: function() {
777 return Detection
.stopDetect();
783 var PointerEvent
= Hammer
.PointerEvent
= {
791 * get a list of pointers
792 * @returns {Array} touchlist
794 getTouchList
: function getTouchList() {
796 // we can use forEach since pointerEvents only is in IE10
797 Utils
.each(this.pointers
, function(pointer
){
798 touchlist
.push(pointer
);
805 * update the position of a pointer
806 * @param {String} type EVENT_END
807 * @param {Object} pointerEvent
809 updatePointer
: function updatePointer(type
, pointerEvent
) {
810 if(type
== EVENT_END
) {
811 delete this.pointers
[pointerEvent
.pointerId
];
814 pointerEvent
.identifier
= pointerEvent
.pointerId
;
815 this.pointers
[pointerEvent
.pointerId
] = pointerEvent
;
818 // it's save to use Object.keys, since pointerEvents are only in newer browsers
819 return Object
.keys(this.pointers
).length
;
823 * check if ev matches pointertype
824 * @param {String} pointerType POINTER_MOUSE
825 * @param {PointerEvent} ev
827 matchType
: function matchType(pointerType
, ev
) {
828 if(!ev
.pointerType
) {
832 var pt
= ev
.pointerType
835 types
[POINTER_MOUSE
] = (pt
=== POINTER_MOUSE
);
836 types
[POINTER_TOUCH
] = (pt
=== POINTER_TOUCH
);
837 types
[POINTER_PEN
] = (pt
=== POINTER_PEN
);
838 return types
[pointerType
];
845 getEvents
: function getEvents() {
847 'pointerdown MSPointerDown',
848 'pointermove MSPointerMove',
849 'pointerup pointercancel MSPointerUp MSPointerCancel'
856 reset
: function resetList() {
862 var Detection
= Hammer
.detection
= {
863 // contains all registred Hammer.gestures in the correct order
866 // data of the current Hammer.gesture detection session
869 // the previous Hammer.gesture session data
870 // is a full clone of the previous gesture.current object
873 // when this becomes true, no gestures are fired
878 * start Hammer.gesture detection
879 * @param {Hammer.Instance} inst
880 * @param {Object} eventData
882 startDetect
: function startDetect(inst
, eventData
) {
883 // already busy with a Hammer.gesture detection on an element
888 this.stopped
= false;
890 // holds current session
892 inst
: inst
, // reference to HammerInstance we're working for
893 startEvent
: Utils
.extend({}, eventData
), // start eventData for distances, timing etc
894 lastEvent
: false, // last eventData
895 lastVelocityEvent
: false, // last eventData for velocity.
896 velocity
: false, // current velocity
897 name
: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
900 this.detect(eventData
);
905 * Hammer.gesture detection
906 * @param {Object} eventData
908 detect
: function detect(eventData
) {
909 if(!this.current
|| this.stopped
) {
913 // extend event data with calculations about scale, distance etc
914 eventData
= this.extendEventData(eventData
);
916 // hammer instance and instance options
917 var inst
= this.current
.inst
,
918 inst_options
= inst
.options
;
920 // call Hammer.gesture handlers
921 Utils
.each(this.gestures
, function triggerGesture(gesture
) {
922 // only when the instance options have enabled this gesture
923 if(!this.stopped
&& inst_options
[gesture
.name
] !== false && inst
.enabled
!== false ) {
924 // if a handler returns false, we stop with the detection
925 if(gesture
.handler
.call(gesture
, eventData
, inst
) === false) {
932 // store as previous event event
934 this.current
.lastEvent
= eventData
;
937 // end event, but not the last touch, so dont stop
938 if(eventData
.eventType
== EVENT_END
&& !eventData
.touches
.length
- 1) {
947 * clear the Hammer.gesture vars
948 * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
949 * to stop other Hammer.gestures from being fired
951 stopDetect
: function stopDetect() {
952 // clone current data to the store as the previous gesture
953 // used for the double tap gesture, since this is an other gesture detect session
954 this.previous
= Utils
.extend({}, this.current
);
967 * @param {Number} delta_time
968 * @param {Number} delta_x
969 * @param {Number} delta_y
971 getVelocityData
: function getVelocityData(ev
, delta_time
, delta_x
, delta_y
) {
972 var cur
= this.current
973 , velocityEv
= cur
.lastVelocityEvent
974 , velocity
= cur
.velocity
;
976 // calculate velocity every x ms
977 if (velocityEv
&& ev
.timeStamp
- velocityEv
.timeStamp
> Hammer
.UPDATE_VELOCITY_INTERVAL
) {
978 velocity
= Utils
.getVelocity(ev
.timeStamp
- velocityEv
.timeStamp
,
979 ev
.center
.clientX
- velocityEv
.center
.clientX
,
980 ev
.center
.clientY
- velocityEv
.center
.clientY
);
981 cur
.lastVelocityEvent
= ev
;
983 else if(!cur
.velocity
) {
984 velocity
= Utils
.getVelocity(delta_time
, delta_x
, delta_y
);
985 cur
.lastVelocityEvent
= ev
;
988 cur
.velocity
= velocity
;
990 ev
.velocityX
= velocity
.x
;
991 ev
.velocityY
= velocity
.y
;
996 * calculate interim angle and direction
999 getInterimData
: function getInterimData(ev
) {
1000 var lastEvent
= this.current
.lastEvent
1004 // end events (e.g. dragend) don't have useful values for interimDirection & interimAngle
1005 // because the previous event has exactly the same coordinates
1006 // so for end events, take the previous values of interimDirection & interimAngle
1007 // instead of recalculating them and getting a spurious '0'
1008 if(ev
.eventType
== EVENT_END
) {
1009 angle
= lastEvent
&& lastEvent
.interimAngle
;
1010 direction
= lastEvent
&& lastEvent
.interimDirection
;
1013 angle
= lastEvent
&& Utils
.getAngle(lastEvent
.center
, ev
.center
);
1014 direction
= lastEvent
&& Utils
.getDirection(lastEvent
.center
, ev
.center
);
1017 ev
.interimAngle
= angle
;
1018 ev
.interimDirection
= direction
;
1023 * extend eventData for Hammer.gestures
1024 * @param {Object} evData
1025 * @returns {Object} evData
1027 extendEventData
: function extendEventData(ev
) {
1028 var cur
= this.current
1029 , startEv
= cur
.startEvent
;
1031 // if the touches change, set the new touches over the startEvent touches
1032 // this because touchevents don't have all the touches on touchstart, or the
1033 // user must place his fingers at the EXACT same time on the screen, which is not realistic
1034 // but, sometimes it happens that both fingers are touching at the EXACT same time
1035 if(ev
.touches
.length
!= startEv
.touches
.length
|| ev
.touches
=== startEv
.touches
) {
1036 // extend 1 level deep to get the touchlist with the touch objects
1037 startEv
.touches
= [];
1038 Utils
.each(ev
.touches
, function(touch
) {
1039 startEv
.touches
.push(Utils
.extend({}, touch
));
1043 var delta_time
= ev
.timeStamp
- startEv
.timeStamp
1044 , delta_x
= ev
.center
.clientX
- startEv
.center
.clientX
1045 , delta_y
= ev
.center
.clientY
- startEv
.center
.clientY
;
1047 this.getVelocityData(ev
, delta_time
, delta_x
, delta_y
);
1048 this.getInterimData(ev
);
1051 startEvent
: startEv
,
1053 deltaTime
: delta_time
,
1057 distance
: Utils
.getDistance(startEv
.center
, ev
.center
),
1058 angle
: Utils
.getAngle(startEv
.center
, ev
.center
),
1059 direction
: Utils
.getDirection(startEv
.center
, ev
.center
),
1061 scale
: Utils
.getScale(startEv
.touches
, ev
.touches
),
1062 rotation
: Utils
.getRotation(startEv
.touches
, ev
.touches
)
1070 * register new gesture
1071 * @param {Object} gesture object, see gestures.js for documentation
1072 * @returns {Array} gestures
1074 register
: function register(gesture
) {
1075 // add an enable gesture options if there is no given
1076 var options
= gesture
.defaults
|| {};
1077 if(options
[gesture
.name
] === undefined) {
1078 options
[gesture
.name
] = true;
1081 // extend Hammer default options with the Hammer.gesture options
1082 Utils
.extend(Hammer
.defaults
, options
, true);
1085 gesture
.index
= gesture
.index
|| 1000;
1087 // add Hammer.gesture to the list
1088 this.gestures
.push(gesture
);
1090 // sort the list by index
1091 this.gestures
.sort(function(a
, b
) {
1092 if(a
.index
< b
.index
) { return -1; }
1093 if(a
.index
> b
.index
) { return 1; }
1097 return this.gestures
;
1104 * Move with x fingers (default 1) around on the page. Blocking the scrolling when
1105 * moving left and right is a good practice. When all the drag events are blocking
1106 * you disable scrolling on that area.
1107 * @events drag, drapleft, dragright, dragup, dragdown
1109 Hammer
.gestures
.Drag
= {
1113 drag_min_distance
: 10,
1115 // Set correct_for_drag_min_distance to true to make the starting point of the drag
1116 // be calculated from where the drag was triggered, not from where the touch started.
1117 // Useful to avoid a jerk-starting drag, which can make fine-adjustments
1118 // through dragging difficult, and be visually unappealing.
1119 correct_for_drag_min_distance
: true,
1121 // set 0 for unlimited, but this can conflict with transform
1122 drag_max_touches
: 1,
1124 // prevent default browser behavior when dragging occurs
1125 // be careful with it, it makes the element a blocking element
1126 // when you are using the drag gesture, it is a good practice to set this true
1127 drag_block_horizontal
: false,
1128 drag_block_vertical
: false,
1130 // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
1131 // It disallows vertical directions if the initial direction was horizontal, and vice versa.
1132 drag_lock_to_axis
: false,
1134 // drag lock only kicks in when distance > drag_lock_min_distance
1135 // This way, locking occurs only when the distance has become large enough to reliably determine the direction
1136 drag_lock_min_distance
: 25
1140 handler
: function dragGesture(ev
, inst
) {
1141 var cur
= Detection
.current
;
1143 // current gesture isnt drag, but dragged is true
1144 // this means an other gesture is busy. now call dragend
1145 if(cur
.name
!= this.name
&& this.triggered
) {
1146 inst
.trigger(this.name
+ 'end', ev
);
1147 this.triggered
= false;
1152 if(inst
.options
.drag_max_touches
> 0 &&
1153 ev
.touches
.length
> inst
.options
.drag_max_touches
) {
1157 switch(ev
.eventType
) {
1159 this.triggered
= false;
1163 // when the distance we moved is too small we skip this gesture
1164 // or we can be already in dragging
1165 if(ev
.distance
< inst
.options
.drag_min_distance
&&
1166 cur
.name
!= this.name
) {
1170 var startCenter
= cur
.startEvent
.center
;
1173 if(cur
.name
!= this.name
) {
1174 cur
.name
= this.name
;
1175 if(inst
.options
.correct_for_drag_min_distance
&& ev
.distance
> 0) {
1176 // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
1177 // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
1178 // It might be useful to save the original start point somewhere
1179 var factor
= Math
.abs(inst
.options
.drag_min_distance
/ ev
.distance
);
1180 startCenter
.pageX
+= ev
.deltaX
* factor
;
1181 startCenter
.pageY
+= ev
.deltaY
* factor
;
1182 startCenter
.clientX
+= ev
.deltaX
* factor
;
1183 startCenter
.clientY
+= ev
.deltaY
* factor
;
1185 // recalculate event data using new start point
1186 ev
= Detection
.extendEventData(ev
);
1190 // lock drag to axis?
1191 if(cur
.lastEvent
.drag_locked_to_axis
||
1192 ( inst
.options
.drag_lock_to_axis
&&
1193 inst
.options
.drag_lock_min_distance
<= ev
.distance
1195 ev
.drag_locked_to_axis
= true;
1197 var last_direction
= cur
.lastEvent
.direction
;
1198 if(ev
.drag_locked_to_axis
&& last_direction
!== ev
.direction
) {
1199 // keep direction on the axis that the drag gesture started on
1200 if(Utils
.isVertical(last_direction
)) {
1201 ev
.direction
= (ev
.deltaY
< 0) ? DIRECTION_UP
: DIRECTION_DOWN
;
1204 ev
.direction
= (ev
.deltaX
< 0) ? DIRECTION_LEFT
: DIRECTION_RIGHT
;
1208 // first time, trigger dragstart event
1209 if(!this.triggered
) {
1210 inst
.trigger(this.name
+ 'start', ev
);
1211 this.triggered
= true;
1215 inst
.trigger(this.name
, ev
);
1216 inst
.trigger(this.name
+ ev
.direction
, ev
);
1218 var is_vertical
= Utils
.isVertical(ev
.direction
);
1220 // block the browser events
1221 if((inst
.options
.drag_block_vertical
&& is_vertical
) ||
1222 (inst
.options
.drag_block_horizontal
&& !is_vertical
)) {
1223 ev
.preventDefault();
1229 if(this.triggered
) {
1230 inst
.trigger(this.name
+ 'end', ev
);
1233 this.triggered
= false;
1241 * Touch stays at the same place for x time
1244 Hammer
.gestures
.Hold
= {
1253 handler
: function holdGesture(ev
, inst
) {
1254 switch(ev
.eventType
) {
1256 // clear any running timers
1257 clearTimeout(this.timer
);
1259 // set the gesture so we can check in the timeout if it still is
1260 Detection
.current
.name
= this.name
;
1262 // set timer and if after the timeout it still is hold,
1263 // we trigger the hold event
1264 this.timer
= setTimeout(function() {
1265 if(Detection
.current
.name
== 'hold') {
1266 inst
.trigger('hold', ev
);
1268 }, inst
.options
.hold_timeout
);
1271 // when you move or end we clear the timer
1273 if(ev
.distance
> inst
.options
.hold_threshold
) {
1274 clearTimeout(this.timer
);
1279 clearTimeout(this.timer
);
1287 * Called as last, tells the user has released the screen
1290 Hammer
.gestures
.Release
= {
1293 handler
: function releaseGesture(ev
, inst
) {
1294 if(ev
.eventType
== EVENT_END
) {
1295 inst
.trigger(this.name
, ev
);
1302 * triggers swipe events when the end velocity is above the threshold
1303 * for best usage, set prevent_default (on the drag gesture) to true
1304 * @events swipe, swipeleft, swiperight, swipeup, swipedown
1306 Hammer
.gestures
.Swipe
= {
1310 swipe_min_touches
: 1,
1311 swipe_max_touches
: 1,
1312 swipe_velocity
: 0.7
1314 handler
: function swipeGesture(ev
, inst
) {
1315 if(ev
.eventType
== EVENT_END
) {
1317 if(ev
.touches
.length
< inst
.options
.swipe_min_touches
||
1318 ev
.touches
.length
> inst
.options
.swipe_max_touches
) {
1322 // when the distance we moved is too small we skip this gesture
1323 // or we can be already in dragging
1324 if(ev
.velocityX
> inst
.options
.swipe_velocity
||
1325 ev
.velocityY
> inst
.options
.swipe_velocity
) {
1326 // trigger swipe events
1327 inst
.trigger(this.name
, ev
);
1328 inst
.trigger(this.name
+ ev
.direction
, ev
);
1336 * Quick touch at a place or double at the same place
1337 * @events tap, doubletap
1339 Hammer
.gestures
.Tap
= {
1343 tap_max_touchtime
: 250,
1344 tap_max_distance
: 10,
1346 doubletap_distance
: 20,
1347 doubletap_interval
: 300
1352 handler
: function tapGesture(ev
, inst
) {
1353 var prev
, since_prev
, did_doubletap
;
1355 // reset moved state
1356 if(ev
.eventType
== EVENT_START
) {
1357 this.has_moved
= false;
1360 // Track the distance we've moved. If it's above the max ONCE, remember that (fixes #406).
1361 else if(ev
.eventType
== EVENT_MOVE
&& !this.moved
) {
1362 this.has_moved
= (ev
.distance
> inst
.options
.tap_max_distance
);
1365 else if(ev
.eventType
== EVENT_END
&&
1366 ev
.srcEvent
.type
!= 'touchcancel' &&
1367 ev
.deltaTime
< inst
.options
.tap_max_touchtime
&& !this.has_moved
) {
1369 // previous gesture, for the double tap since these are two different gesture detections
1370 prev
= Detection
.previous
;
1371 since_prev
= prev
&& prev
.lastEvent
&& ev
.timeStamp
- prev
.lastEvent
.timeStamp
;
1372 did_doubletap
= false;
1374 // check if double tap
1375 if(prev
&& prev
.name
== 'tap' &&
1376 (since_prev
&& since_prev
< inst
.options
.doubletap_interval
) &&
1377 ev
.distance
< inst
.options
.doubletap_distance
) {
1378 inst
.trigger('doubletap', ev
);
1379 did_doubletap
= true;
1383 if(!did_doubletap
|| inst
.options
.tap_always
) {
1384 Detection
.current
.name
= 'tap';
1385 inst
.trigger(Detection
.current
.name
, ev
);
1393 * Called as first, tells the user has touched the screen
1396 Hammer
.gestures
.Touch
= {
1400 // call preventDefault at touchstart, and makes the element blocking by
1401 // disabling the scrolling of the page, but it improves gestures like
1402 // transforming and dragging.
1403 // be careful with using this, it can be very annoying for users to be stuck
1405 prevent_default
: false,
1407 // disable mouse events, so only touch (or pen!) input triggers events
1408 prevent_mouseevents
: false
1410 handler
: function touchGesture(ev
, inst
) {
1411 if(inst
.options
.prevent_mouseevents
&&
1412 ev
.pointerType
== POINTER_MOUSE
) {
1417 if(inst
.options
.prevent_default
) {
1418 ev
.preventDefault();
1421 if(ev
.eventType
== EVENT_START
) {
1422 inst
.trigger(this.name
, ev
);
1430 * User want to scale or rotate with 2 fingers
1431 * @events transform, pinch, pinchin, pinchout, rotate
1433 Hammer
.gestures
.Transform
= {
1437 // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
1438 transform_min_scale
: 0.01,
1439 // rotation in degrees
1440 transform_min_rotation
: 1,
1441 // prevent default browser behavior when two touches are on the screen
1442 // but it makes the element a blocking element
1443 // when you are using the transform gesture, it is a good practice to set this true
1444 transform_always_block
: false,
1445 // ensures that all touches occurred within the instance element
1446 transform_within_instance
: false
1451 handler
: function transformGesture(ev
, inst
) {
1452 // current gesture isnt drag, but dragged is true
1453 // this means an other gesture is busy. now call dragend
1454 if(Detection
.current
.name
!= this.name
&& this.triggered
) {
1455 inst
.trigger(this.name
+ 'end', ev
);
1456 this.triggered
= false;
1460 // at least multitouch
1461 if(ev
.touches
.length
< 2) {
1465 // prevent default when two fingers are on the screen
1466 if(inst
.options
.transform_always_block
) {
1467 ev
.preventDefault();
1470 // check if all touches occurred within the instance element
1471 if(inst
.options
.transform_within_instance
) {
1472 for(var i
=-1; ev
.touches
[++i
];) {
1473 if(!Utils
.hasParent(ev
.touches
[i
].target
, inst
.element
)) {
1479 switch(ev
.eventType
) {
1481 this.triggered
= false;
1485 var scale_threshold
= Math
.abs(1 - ev
.scale
);
1486 var rotation_threshold
= Math
.abs(ev
.rotation
);
1488 // when the distance we moved is too small we skip this gesture
1489 // or we can be already in dragging
1490 if(scale_threshold
< inst
.options
.transform_min_scale
&&
1491 rotation_threshold
< inst
.options
.transform_min_rotation
) {
1495 // we are transforming!
1496 Detection
.current
.name
= this.name
;
1498 // first time, trigger dragstart event
1499 if(!this.triggered
) {
1500 inst
.trigger(this.name
+ 'start', ev
);
1501 this.triggered
= true;
1504 inst
.trigger(this.name
, ev
); // basic transform event
1506 // trigger rotate event
1507 if(rotation_threshold
> inst
.options
.transform_min_rotation
) {
1508 inst
.trigger('rotate', ev
);
1511 // trigger pinch event
1512 if(scale_threshold
> inst
.options
.transform_min_scale
) {
1513 inst
.trigger('pinch', ev
);
1514 inst
.trigger('pinch' + (ev
.scale
<1 ? 'in' : 'out'), ev
);
1520 if(this.triggered
) {
1521 inst
.trigger(this.name
+ 'end', ev
);
1524 this.triggered
= false;
1531 if(typeof define
== 'function' && define
.amd
) {
1537 else if(typeof module
== 'object' && module
.exports
) {
1538 module
.exports
= Hammer
;
1542 window
.Hammer
= Hammer
;