2 * Hammer.JS - v1.0.5 - 2013-04-07
\r
3 * http://eightmedia.github.com/hammer.js
\r
4 * Jorik Tangelder <j.tangelder@gmail.com>, MIT license
\r
7 ( function( Math, window, document, undefined ){
\r
9 var ELEENT_LIST = [],
\r
12 ABS = new Function( 'v', 'return v<0?-v:v' );
\r
14 X.UI.Gesture = Hammer;
\r
16 function Hammer( uinodeRoot, uinode, type ){
\r
17 this.uinode = uinode;
\r
18 this.enabled = true;
\r
20 Hammer.startup && Hammer.startup( uinodeRoot );
\r
22 this.options = Hammer.defaults;
\r
24 // start detection on touchstart
\r
25 Utils.addEvents( uinode, Hammer.EVENT_TYPES_START, this );
\r
27 this.listen( type );
\r
30 Hammer.defaults = {};
\r
32 Hammer.prototype.handleEvent = function( e ){
\r
33 //var sourceEventType = e.type.toLowerCase();
\r
35 var type = IdToGestureID[ e.type ],
\r
36 gestures = Detection.gestures,
\r
37 numTouches = 0,// count the total touches on the screen
\r
38 pointerType, i, l, touches, ret, active, gesture, startEv,
\r
39 hammer, deltaTime, deltaX, deltaY, velocity, center;
\r
41 //console.log( 'Hammer@handleEvent ' + X.UI.Event.IdToName[ e.type ] + ' ' + e.pointerType + ' ' + type );
\r
44 //console.log( e.type + ' dw:' + X.UI.Event._POINTER_DOWN + ' up:' + X.UI.Event._POINTER_UP + ' mv:' + X.UI.Event._POINTER_MOVE );
\r
46 if( e.pointerType ){
\r
48 switch( e.pointerType ){
\r
50 case 2 : //e.MSPOINTER_TYPE_TOUCH :
\r
51 type |= TOUCH; break;
\r
53 case 3 : //e.MSPOINTER_TYPE_PEN :
\r
56 case 4 : //e.MSPOINTER_TYPE_MOUSE :
\r
57 type |= MOUSE; break;
\r
68 // onmouseup, but when touchend has been fired we do nothing.
\r
69 // this is for touchdevices which also fire a mouseup on touchend
\r
70 if( type & MOUSE && touch_triggered ){
\r
71 return X.Callback.STOP_NOW | X.Callback.PREVENT_DEFAULT;
\r
73 // mousebutton must be down or a touch event
\r
75 type & TOUCH || //sourceEventType.match(/touch/) || // touch events are always on screen
\r
76 ( type & POINTER && type & START ) || //sourceEventType.match(/pointerdown/) || // pointerevents touch
\r
77 ( type & MOUSE && e.button === 0 ) //(sourceEventType.match(/mouse/) && e.which === 1) // mouse is pressed
\r
79 enable_detect = true;
\r
82 console.log( 'Hammer@handleEvent ' + IdToGestureID[ e.type ] + ' ' + e.type + ' ' + X.UI.Event._POINTER_DOWN + ' ' + enable_detect );
\r
84 // we are in a touch event, set the touch triggered bool to true,
\r
85 // this for the conflicts that may occur on ios and android
\r
86 //type & ( TOUCH | POINTER ) && ( touch_triggered = true );
\r
87 type & TOUCH && ( touch_triggered = true );
\r
88 //if (sourceEventType.match(/touch|pointer/)) { touch_triggered = true;}
\r
90 // when touch has been triggered in this detection session
\r
91 // and we are now handling a mouse event, we stop that to prevent conflicts
\r
92 if( enable_detect ){
\r
93 // update pointerevent
\r
94 if( Hammer.HAS_POINTEREVENTS ){ //eventType !== Hammer.EVENT_END ){
\r
95 POINTERS[ e.identifier = e.pointerId ] = type & END ? null : e;
\r
97 // we can use forEach since pointerEvents only is in IE10
\r
98 for( i = 0, l = POINTERS.length; i < l; ++i ){
\r
99 POINTERS[ i ] && ( touches[ touches.length ] = POINTERS[ i ] );
\r
101 numTouches = touches.length;
\r
103 console.log( 'numTouches ' + numTouches );
\r
106 if ( type & TOUCH ){ //sourceEventType.match(/touch/)) {
\r
107 touches = Hammer.DO_TOUCHES_FIX && type & END ? [] : e.touches;
\r
108 numTouches = touches.length;
\r
111 if( !touch_triggered ){
\r
112 numTouches = ( type & END ) ? 0 : 1;
\r
113 touches = numTouches === 0 ? [] : [{
\r
120 // if we are in a end event, but when we remove one touch and
\r
121 // we still have enough, set eventType to move
\r
122 if( 0 < numTouches && type & END ){ // eventType === Hammer.EVENT_END ){
\r
123 type = type & POINTER_TYPE_MASK | MOVE;
\r
124 //eventType = Hammer.EVENT_MOVE;
\r
125 } else if( !numTouches ){
\r
126 // no touches, force the end event
\r
127 type = type & POINTER_TYPE_MASK | END;
\r
128 //eventType = Hammer.EVENT_END;
\r
131 // because touchend has no touches, and we often want to use these in our gestures,
\r
132 // we send the last move event as our eventData in touchend
\r
133 ( !numTouches && last_move_event !== null ) ?
\r
134 ( e = last_move_event ):
\r
135 ( last_move_event = e ); // store the last move event
\r
138 center : Utils.getCenter( touches ),
\r
139 timeStamp : e.timeStamp,
\r
142 eventType : type & EVENT_TYPE_MASK,
\r
143 pointerType : type & POINTER_TYPE_MASK
\r
146 if( type & START ){
\r
147 if( !this.enabled ) return;
\r
148 // already busy with a Hammer.gesture detection on an element
\r
149 if( Detection.current ) return;
\r
150 Detection.current = {
\r
151 hammer : this, // reference to HammerInstance we're working for
\r
152 startEvent : Utils.extend( {}, e ), // start eventData for distances, timing etc
\r
153 lastEvent : false, // last eventData
\r
154 name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc
\r
156 Detection.stopped = false;
\r
158 active = hammer.activeGesture;
\r
160 if( !Detection.current || Detection.stopped ){
\r
163 hammer = Detection.current.hammer;
\r
164 active = hammer.activeGesture;
\r
167 // ----------------------------------------------------------------------------------------------------------------
\r
168 // ret = Detection.detect( e );
\r
170 // ----------------------------------------------------------------------------------------------------------------
\r
171 // extend event data with calculations about scale, distance etc
\r
172 // e = Detection.extendEventData( e );
\r
173 startEv = Detection.current.startEvent;
\r
176 // if the touches change, set the new touches over the startEvent touches
\r
177 // this because touchevents don't have all the touches on touchstart, or the
\r
178 // user must place his fingers at the EXACT same time on the screen, which is not realistic
\r
179 // but, sometimes it happens that both fingers are touching at the EXACT same time
\r
180 if( startEv && ( numTouches !== startEv.touches.length || touches === startEv.touches ) ){
\r
181 // extend 1 level deep to get the touchlist with the touch objects
\r
182 startEv.touches.length = i = 0;
\r
183 for( ; i < numTouches; ++i ){
\r
184 startEv.touches[ startEv.touches.length ] = Utils.extend( {}, touches[ i ] );
\r
188 deltaTime = e.timeStamp - startEv.timeStamp;
\r
189 deltaX = center.pageX - startEv.center.pageX;
\r
190 deltaY = center.pageY - startEv.center.pageY;
\r
191 velocity = Utils.getVelocity( deltaTime, deltaX, deltaY );
\r
194 deltaTime : deltaTime,
\r
199 velocityX : velocity.x,
\r
200 velocityY : velocity.y,
\r
202 distance : Utils.getDistance( startEv.center, center ),
\r
203 angle : Utils.getAngle( startEv.center, center ),
\r
204 direction : Utils.getDirection( startEv.center, center ),
\r
206 scale : Utils.getScale( startEv.touches, touches ),
\r
207 rotation : Utils.getRotation( startEv.touches, touches ),
\r
209 startEvent : startEv
\r
212 // store as previous event event
\r
213 Detection.current.lastEvent = e;
\r
215 // call Hammer.gesture handlers
\r
216 for( i = 0, l = gestures.length; i < l; ++i ){
\r
217 gesture = gestures[ i ];
\r
218 if( Detection.stopped ) break;
\r
219 //if( active[ gesture.name ] ) console.log( gesture.name );
\r
220 // only when the instance options have enabled this gesture
\r
221 active[ gesture.name ] &&
\r
222 // if a handler returns false, we stop with the detection
\r
223 ( ret |= ( gesture.handler( e, hammer ) || X.Callback.NONE ) );
\r
226 // endevent, but not the last touch, so dont stop
\r
227 type & END && numTouches === 0 && Detection.stopDetect();
\r
229 // ----------------------------------------------------------------------------------------------------------------
\r
230 // trigger the handler
\r
231 //handler.call( context, HamEvent.collectEventData( element, eventType, e ) );
\r
233 // remove pointerevent from list
\r
234 if( Hammer.HAS_POINTEREVENTS && type & END ){ // eventType === Hammer.EVENT_END ){
\r
239 //debug(sourceEventType +" "+ eventType);
\r
241 // on the end we reset everything
\r
242 if( numTouches === 0 ){
\r
243 last_move_event = null;
\r
244 enable_detect = false;
\r
245 touch_triggered = false;
\r
246 POINTERS.length = 0;
\r
252 Hammer.startup = function( uinodeRoot ){
\r
253 // find what eventtypes we add listeners to
\r
255 * we have different events for each device/browser
\r
256 * determine what we need and set them in the Hammer.EVENT_TYPES constant
\r
258 // determine the eventtype we want to set
\r
259 // for non pointer events browsers and mixed browsers,
\r
260 // like chrome on windows8 touch laptop
\r
263 // Register all gestures inside Gestures
\r
264 for( name in Gestures ){
\r
265 //Gestures.hasOwnProperty( name ) &&
\r
266 Detection.register( Gestures[ name ] );
\r
269 //if( X_UA_HID.POINTER ){
\r
270 Hammer.EVENT_TYPES_START = [ X.UI.Event._POINTER_DOWN ];
\r
271 types = [ X.UI.Event._POINTER_MOVE, X.UI.Event._POINTER_UP, X.UI.Event._POINTER_CANCEL ];
\r
273 if( X_UA_HID.TOUCH ){
\r
274 Hammer.EVENT_TYPES_START = [ X.UI.Event._TOUCH_START, X.UI.Event._MOUSE_DOWN ];
\r
275 types = [ X.UI.Event._MOUSE_MOVE, X.UI.Event._MOUSE_UP, X.UI.Event._MOUSE_CANCEL,
\r
276 X.UI.Event._TOUCH_MOVE, X.UI.Event._TOUCH_END, X.UI.Event._TOUCH_CANCEL ];
\r
278 Hammer.EVENT_TYPES_START = [ X.UI.Event._MOUSE_DOWN ];
\r
279 types = [ X.UI.Event._MOUSE_MOVE, X.UI.Event._MOUSE_UP, X.UI.Event._MOUSE_CANCEL ];
\r
282 // Add touch events on the document
\r
283 Utils.addEvents( uinodeRoot, types, Hammer.prototype.handleEvent );
\r
285 // Hammer is ready...!
\r
286 delete Hammer.startup;
\r
289 Hammer.prototype.trigger = function( type, gesture ){
\r
290 if( !this.types[ type ] ) return;
\r
291 var e = Utils.extend( {}, gesture );
\r
293 return this.uinode.dispatch( e );
\r
296 Hammer.prototype.listen = function( type ){
\r
297 var gestures = Detection.gestures,
\r
298 i = gestures.length, g;
\r
300 g = gestures[ --i ];
\r
301 if( g.startID <= type && type <= g.endID ){
\r
302 if( !this.activeGesture ) this.activeGesture = {};
\r
303 if( !this.types ) this.types = {};
\r
304 this.activeGesture[ g.name ] = this.types[ type ] = 1;
\r
310 Hammer.prototype.unlisten = function( type ){
\r
311 var gestures = Detection.gestures,
\r
312 i = gestures.length, g;
\r
313 if( !this.activeGesture ) return;
\r
315 g = gestures[ --i ];
\r
316 if( g.startID <= type && type <= g.endID ){
\r
317 if( this.activeGesture[ g.name ] ){
\r
318 if( this.types[ type ] ) delete this.types[ type ];
\r
319 for( i = g.startID; i <= g.endID; ++i ){
\r
320 if( this.types[ i ] ) return;
\r
322 delete this.activeGesture[ g.name ];
\r
330 * "Android version < 2.2" return ev.touches.length === 1 when touchend, others return ev.touches.length === 0
\r
332 Hammer.DO_TOUCHES_FIX = Hammer.HAS_TOUCHEVENTS && ( X_UA.Android < 2.2 || X_UA.Blink || X_UA.Opera );
\r
334 // detect touchevents
\r
335 Hammer.HAS_POINTEREVENTS = true; // navigator.pointerEnabled || navigator.msPointerEnabled;
\r
336 Hammer.HAS_POINTEREVENTS && console.log( 'Hammer.HAS_POINTEREVENTS : true' );
\r
339 // eventtypes per touchevent (start, move, end)
\r
340 // are filled by HamEvent.determineEventTypes on setup
\r
341 Hammer.EVENT_TYPES_START = null;
\r
343 // direction defines
\r
344 Hammer.DIRECTION_DOWN = 'down';
\r
345 Hammer.DIRECTION_LEFT = 'left';
\r
346 Hammer.DIRECTION_UP = 'up';
\r
347 Hammer.DIRECTION_RIGHT = 'right';
\r
349 // plugins namespace
\r
350 Hammer.plugins = {};
\r
360 EVENT_TYPE_MASK = START | MOVE | END,
\r
361 POINTER_TYPE_MASK = POINTER | TOUCH | MOUSE | PEN,
\r
362 IdToGestureID = {};
\r
363 IdToGestureID[ X.UI.Event._POINTER_DOWN ] = START;
\r
364 IdToGestureID[ X.UI.Event._POINTER_MOVE ] = MOVE;
\r
365 IdToGestureID[ X.UI.Event._POINTER_UP ] = END;
\r
366 IdToGestureID[ X.UI.Event._POINTER_CANCEL ] = END;
\r
368 IdToGestureID[ X.UI.Event._TOUCH_START ] = START;
\r
369 IdToGestureID[ X.UI.Event._TOUCH_MOVE ] = MOVE;
\r
370 IdToGestureID[ X.UI.Event._TOUCH_END ] = END;
\r
371 IdToGestureID[ X.UI.Event._TOUCH_CANCEL ] = END;
\r
373 IdToGestureID[ X.UI.Event._MOUSE_DOWN ] = START;
\r
374 IdToGestureID[ X.UI.Event._MOUSE_MOVE ] = MOVE;
\r
375 IdToGestureID[ X.UI.Event._MOUSE_UP ] = END;
\r
376 IdToGestureID[ X.UI.Event._MOUSE_CANCEL ] = END;
\r
381 * touch events with mouse fallback
\r
382 * @param {HTMLElement} element
\r
383 * @param {String} eventType like Hammer.EVENT_MOVE
\r
384 * @param {Function} handler
\r
386 addEvents : function( uinode, types, context ){
\r
387 for( var i = 0; i < types.length; ++i ){
\r
388 uinode.listen( types[ i ], context );
\r
394 * also used for cloning when dest is an empty object
\r
395 * @param {Object} dest
\r
396 * @param {Object} src
\r
397 * @parm {Boolean} merge do a merge
\r
398 * @returns {Object} dest
\r
400 extend : function extend( dest, src, merge ){
\r
401 for( var key in src ){
\r
402 if( dest[ key ] !== undefined && merge ) continue;
\r
403 dest[ key ] = src[ key ];
\r
409 * find if a node is in the given parent
\r
410 * used for event delegation tricks
\r
411 * @param {HTMLElement} node
\r
412 * @param {HTMLElement} parent
\r
413 * @returns {boolean} has_parent
\r
415 hasParent : function( node, parent ){
\r
416 while( node && node.tagName ){ /* tagName for ie */
\r
417 if( node === parent ) return true;
\r
418 node = node.parentNode;
\r
424 * get the center of all the touches
\r
425 * @param {Array} touches
\r
426 * @returns {Object} center
\r
428 getCenter : function getCenter(touches) {
\r
430 l = touches.length,
\r
437 pageX : touches[ 0 ].pageX,
\r
438 pageY : touches[ 0 ].pageY
\r
442 pageX : ( touches[ 0 ].pageX + touches[ 1 ].pageX ) / 2,
\r
443 pageY : ( touches[ 0 ].pageY + touches[ 1 ].pageY ) / 2
\r
448 for( ; i < l; ++i ){
\r
449 valuesX[ valuesX.length ] = touches[ i ].pageX;
\r
450 valuesY[ valuesY.length ] = touches[ i ].pageY;
\r
453 pageX : ( ( Math.min.apply( null, valuesX ) + Math.max.apply( null, valuesX ) ) / 2 ),
\r
454 pageY : ( ( Math.min.apply( null, valuesY ) + Math.max.apply( null, valuesY ) ) / 2 )
\r
459 * calculate the velocity between two points
\r
460 * @param {Number} deltaTime
\r
461 * @param {Number} deltaX
\r
462 * @param {Number} deltaY
\r
463 * @returns {Object} velocity
\r
465 getVelocity : function getVelocity( deltaTime, deltaX, deltaY ) {
\r
467 x : ABS( deltaX / deltaTime ) || 0,
\r
468 y : ABS( deltaY / deltaTime ) || 0
\r
473 * calculate the angle between two coordinates
\r
474 * @param {Touch} touch1
\r
475 * @param {Touch} touch2
\r
476 * @returns {Number} angle
\r
478 getAngle : function getAngle(touch1, touch2) {
\r
479 var y = touch2.pageY - touch1.pageY,
\r
480 x = touch2.pageX - touch1.pageX;
\r
481 return Math.atan2( y, x ) * 180 / Math.PI;
\r
485 * angle to direction define
\r
486 * @param {Touch} touch1
\r
487 * @param {Touch} touch2
\r
488 * @returns {String} direction constant, like Hammer.DIRECTION_LEFT
\r
490 getDirection : function getDirection( touch1, touch2 ){
\r
491 var x = touch1.pageX - touch2.pageX,
\r
492 y = touch1.pageY - touch2.pageY;
\r
493 return ABS( y ) <= ABS( x ) ?
\r
494 ( x > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT ) :
\r
495 ( y > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN );
\r
499 * calculate the distance between two touches
\r
500 * @param {Touch} touch1
\r
501 * @param {Touch} touch2
\r
502 * @returns {Number} distance
\r
504 getDistance : function getDistance( touch1, touch2 ){
\r
505 var x = touch2.pageX - touch1.pageX,
\r
506 y = touch2.pageY - touch1.pageY;
\r
507 return Math.sqrt( ( x * x ) + ( y * y ) );
\r
511 * calculate the scale factor between two touchLists (fingers)
\r
512 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
\r
513 * @param {Array} start
\r
514 * @param {Array} end
\r
515 * @returns {Number} scale
\r
517 getScale : function getScale( start, end ){
\r
518 // need two fingers...
\r
519 return ( 2 <= start.length && 2 <= end.length ) ?
\r
520 Utils.getDistance( end[ 0 ], end[ 1 ] ) / Utils.getDistance( start[ 0 ], start[ 1 ] ) :
\r
525 * calculate the rotation degrees between two touchLists (fingers)
\r
526 * @param {Array} start
\r
527 * @param {Array} end
\r
528 * @returns {Number} rotation
\r
530 getRotation : function getRotation( start, end ){
\r
531 // need two fingers
\r
532 return ( 2 <= start.length && 2 <= end.length ) ?
\r
533 Utils.getAngle( end[ 1 ], end[ 0 ] ) - Utils.getAngle( start[ 1 ], start[ 0 ] ) :
\r
538 * boolean if the direction is vertical
\r
539 * @param {String} direction
\r
540 * @returns {Boolean} is_vertical
\r
542 isVertical : function isVertical( direction ){
\r
543 return direction === Hammer.DIRECTION_UP || direction === Hammer.DIRECTION_DOWN;
\r
548 * this holds the last move event,
\r
549 * used to fix empty touchend issue
\r
550 * see the onTouch event for an explanation
\r
553 var last_move_event = null;
\r
556 * when the mouse is hold down, this is true
\r
559 var enable_detect = false;
\r
562 * when touch events have been fired, this is true
\r
565 var touch_triggered = false;
\r
568 // contains all registred Gestures in the correct order
\r
571 // data of the current Hammer.gesture detection session
\r
574 // the previous Hammer.gesture session data
\r
575 // is a full clone of the previous gesture.current object
\r
578 // when this becomes true, no gestures are fired
\r
582 * clear the Hammer.gesture vars
\r
583 * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
\r
584 * to stop other Gestures from being fired
\r
586 stopDetect : function stopDetect() {
\r
587 // clone current data to the store as the previous gesture
\r
588 // used for the double tap gesture, since this is an other gesture detect session
\r
589 Detection.previous = Utils.extend( {}, Detection.current );
\r
591 // reset the current
\r
592 Detection.current = null;
\r
595 Detection.stopped = true;
\r
599 * register new gesture
\r
600 * @param {Object} gesture object, see gestures.js for documentation
\r
601 * @returns {Array} gestures
\r
603 register : function( gesture ){
\r
604 // add an enable gesture options if there is no given
\r
605 var options = gesture.defaults || {},
\r
606 list = Detection.gestures,
\r
607 _index, i = 0, l = list.length, index;
\r
608 if( options[ gesture.name ] === undefined ) options[ gesture.name ] = true;
\r
610 // extend Hammer default options with the Hammer.gesture options
\r
611 Utils.extend( Hammer.defaults, options, true );
\r
614 gesture.index = gesture.index || 1000;
\r
616 // add Hammer.gesture to the list
\r
617 //Detection.gestures.push( gesture );
\r
619 // sort the list by index
\r
620 //Detection.gestures.sort( function( a, b ){
\r
622 // a.index < b.index ? -1 :
\r
623 // a.index > b.index ? 1 : 0;
\r
626 list[ 0 ] = gesture;
\r
629 _index = gesture.index;
\r
630 for( i = 0; i < l; ++i ){
\r
631 index = list[ i ].index;
\r
632 if( i === 0 && _index < index ){
\r
633 list.unshift( gesture );
\r
637 list[ l ] = gesture;
\r
640 if( index <= _index && _index < list[ i + 1 ].index ){
\r
641 list.splice( i, 0, gesture );
\r
648 var Gestures = Gestures || {};
\r
652 * ==============================
\r
655 * --------------------
\r
656 * The object structure of a gesture:
\r
658 * { name: 'mygesture',
\r
661 * mygesture_option: true
\r
663 * handler: function(type, e, inst) {
\r
664 * // trigger gesture event
\r
665 * inst.trigger(this.name, e );
\r
669 * @param {String} name
\r
670 * this should be the name of the gesture, lowercase
\r
671 * it is also being used to disable/enable the gesture per instance config.
\r
673 * @param {Number} [index=1000]
\r
674 * the index of the gesture, where it is going to be in the stack of gestures detection
\r
675 * like when you build an gesture that depends on the drag gesture, it is a good
\r
676 * idea to place it after the index of the drag gesture.
\r
678 * @param {Object} [defaults={}]
\r
679 * the default settings of the gesture. these are added to the instance settings,
\r
680 * and can be overruled per instance. you can also add the name of the gesture,
\r
681 * but this is also added by default (and set to true).
\r
683 * @param {Function} handler
\r
684 * this handles the gesture detection of your custom gesture and receives the
\r
685 * following arguments:
\r
687 * @param {Object} eventData
\r
688 * event data containing the following properties:
\r
689 * timeStamp {Number} time the event occurred
\r
690 * target {HTMLElement} target element
\r
691 * touches {Array} touches (fingers, pointers, mouse) on the screen
\r
692 * pointerType {String} kind of pointer that was used. matches Hammer.POINTER_MOUSE|TOUCH
\r
693 * center {Object} center position of the touches. contains pageX and pageY
\r
694 * deltaTime {Number} the total time of the touches in the screen
\r
695 * deltaX {Number} the delta on x axis we haved moved
\r
696 * deltaY {Number} the delta on y axis we haved moved
\r
697 * velocityX {Number} the velocity on the x
\r
698 * velocityY {Number} the velocity on y
\r
699 * angle {Number} the angle we are moving
\r
700 * direction {String} the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT
\r
701 * distance {Number} the distance we haved moved
\r
702 * scale {Number} scaling of the touches, needs 2 touches
\r
703 * rotation {Number} rotation of the touches, needs 2 touches *
\r
704 * eventType {String} matches Hammer.EVENT_START|MOVE|END
\r
705 * srcEvent {Object} the source event, like TouchStart or MouseDown *
\r
706 * startEvent {Object} contains the same properties as above,
\r
707 * but from the first touch. this is used to calculate
\r
708 * distances, deltaTime, scaling etc
\r
710 * @param {Hammer.Instance} inst
\r
711 * the instance we are doing the detection for. you can get the options from
\r
712 * the inst.options object and trigger the gesture event by calling inst.trigger
\r
716 * --------------------
\r
717 * inside the handler you can get/set Detection.current. This is the current
\r
718 * detection session. It has the following properties
\r
719 * @param {String} name
\r
720 * contains the name of the gesture we have detected. it has not a real function,
\r
721 * only to check in other gestures if something is detected.
\r
722 * like in the drag gesture we set it to 'drag' and in the swipe gesture we can
\r
723 * check if the current gesture is 'drag' by accessing Detection.current.name
\r
726 * @param {Hammer.Instance} inst
\r
727 * the instance we do the detection for
\r
730 * @param {Object} startEvent
\r
731 * contains the properties of the first gesture detection in this session.
\r
732 * Used for calculations about timing, distance, etc.
\r
735 * @param {Object} lastEvent
\r
736 * contains all the properties of the last gesture detect in this session.
\r
738 * after the gesture detection session has been completed (user has released the screen)
\r
739 * the Detection.current object is copied into Detection.previous,
\r
740 * this is usefull for gestures like doubletap, where you need to know if the
\r
741 * previous gesture was a tap
\r
743 * options that have been set by the instance can be received by calling inst.options
\r
745 * You can trigger a gesture event by calling inst.trigger("mygesture", event).
\r
746 * The first param is the name of your gesture, the second the event argument
\r
749 * Register gestures
\r
750 * --------------------
\r
751 * When an gesture is added to the Gestures object, it is auto registered
\r
752 * at the setup of the first Hammer instance. You can also call Detection.register
\r
753 * manually and pass your gesture object as a param
\r
759 * Touch stays at the same place for x time
\r
760 * @events hold holdend
\r
765 startID : X.UI.Event.HOLD,
\r
766 endID : X.UI.Event.HOLD_END,
\r
768 hold_timeout : 500,
\r
773 handler : function holdGesture( e, hammer ){
\r
774 switch( e.eventType ){
\r
776 // clear any running timers
\r
777 this.timerID && X.Timer.remove( this.timerID );
\r
779 // set the gesture so we can check in the timeout if it still is
\r
780 Detection.current.name = this.name;
\r
781 Gestures.Hold.holding = false;
\r
783 // set timer and if after the timeout it still is hold,
\r
784 // we trigger the hold event
\r
785 this.timerID = X.Timer.add( hammer.options.hold_timeout, 1, Gestures.Hold._onTimer, [ e, hammer ] );
\r
788 // when you move or end we clear the timer
\r
790 if( e.distance <= hammer.options.hold_threshold ) return;
\r
792 this.timerID && X.Timer.remove( this.timerID );
\r
793 if( Gestures.Hold.holding === true ){
\r
794 Gestures.Hold.holding = false;
\r
795 return hammer.trigger( X.UI.Event.HOLD_END, e );
\r
800 _onTimer : function( e, hammer ){
\r
801 if( Detection.current.name === 'hold' ){
\r
802 hammer.trigger( X.UI.Event.HOLD, e );
\r
803 Gestures.Hold.holding = true;
\r
810 * Quick touch at a place or double at the same place
\r
811 * @events tap, doubletap
\r
816 startID : X.UI.Event.TAP,
\r
817 endID : X.UI.Event.DOUBLE_TAP,
\r
819 tap_max_touchtime : 250,
\r
820 tap_max_distance : 10,
\r
822 doubletap_distance : 20,
\r
823 doubletap_interval : 300
\r
825 handler : function tapGesture( e, hammer ){
\r
826 // previous gesture, for the double tap since these are two different gesture detections
\r
827 var prev = Detection.previous;
\r
828 if( e.eventType === END ){
\r
829 // when the touchtime is higher then the max touch time
\r
830 // or when the moving distance is too much
\r
831 if( hammer.options.tap_max_touchtime < e.deltaTime || hammer.options.tap_max_distance < e.distance ) return;
\r
833 // check if double tap
\r
834 if( prev && prev.name === 'tap' && ( e.timeStamp - prev.lastEvent.timeStamp ) < hammer.options.doubletap_interval && e.distance < hammer.options.doubletap_distance ){
\r
835 return hammer.trigger( X.UI.Event.DOUBLE_TAP, e );
\r
838 if( hammer.options.tap_always && Detection.current.name !== 'tap' ){ // EventFire中にalert すると mouseleave で再び呼ばれるのを防ぐ
\r
839 Detection.current.name = 'tap';
\r
840 return hammer.trigger( X.UI.Event.TAP, e );
\r
848 * triggers swipe events when the end velocity is above the threshold
\r
849 * @events swipe, swipeleft, swiperight, swipeup, swipedown
\r
854 startID : X.UI.Event.SWIP,
\r
855 endID : X.UI.Event.SWIP_DOWN,
\r
857 // set 0 for unlimited, but this can conflict with transform
\r
858 swipe_max_touches : 1,
\r
859 swipe_velocity : 0.7
\r
861 handler : function swipeGesture(e, hammer) {
\r
862 if( e.eventType === END ){
\r
864 if( 0 < hammer.options.swipe_max_touches && hammer.options.swipe_max_touches < e.touches.length ) return;
\r
866 // when the distance we moved is too small we skip this gesture
\r
867 // or we can be already in dragging
\r
868 if( hammer.options.swipe_velocity < e.velocityX || hammer.options.swipe_velocity < e.velocityY ){
\r
869 // trigger swipe events
\r
870 hammer.trigger( X.UI.Event.SWIP, e );
\r
872 e.direction === Hammer.DIRECTION_UP ?
\r
873 X.UI.Event.SWIP_UP :
\r
874 e.direction === Hammer.DIRECTION_DOWN ?
\r
875 X.UI.Event.SWIP_DOWN :
\r
876 e.direction === Hammer.DIRECTION_LEFT ?
\r
877 X.UI.Event.SWIP_LEFT :
\r
878 X.UI.Event.SWIP_RIGHT,
\r
888 * Move with x fingers (default 1) around on the page. Blocking the scrolling when
\r
889 * moving left and right is a good practice. When all the drag events are blocking
\r
890 * you disable scrolling on that area.
\r
891 * @events drag, dragstart, dragend, drapleft, dragright, dragup, dragdown
\r
896 startID : X.UI.Event.DRAG,
\r
897 endID : X.UI.Event.DRAG_DOWN,
\r
899 drag_min_distance : 10,
\r
900 // set 0 for unlimited, but this can conflict with transform
\r
901 drag_max_touches : 1,
\r
902 // prevent default browser behavior when dragging occurs
\r
903 // be careful with it, it makes the element a blocking element
\r
904 // when you are using the drag gesture, it is a good practice to set this true
\r
905 drag_block_horizontal : false,
\r
906 drag_block_vertical : false,
\r
907 // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
\r
908 // It disallows vertical directions if the initial direction was horizontal, and vice versa.
\r
909 drag_lock_to_axis : false,
\r
910 // drag lock only kicks in when distance > drag_lock_min_distance
\r
911 // This way, locking occurs only when the distance has become large enough to reliably determine the direction
\r
912 drag_lock_min_distance : 25
\r
915 handler : function dragGesture( e, hammer ){
\r
916 var last_direction;
\r
917 // current gesture isnt drag, but dragged is true
\r
918 // this means an other gesture is busy. now call dragend
\r
919 if( Detection.current.name !== this.name && this.triggered ){
\r
920 hammer.trigger( X.UI.Event.DRAG_END, e );
\r
921 this.triggered = false;
\r
926 if( 0 < hammer.options.drag_max_touches && hammer.options.drag_max_touches < e.touches.length ) return;
\r
928 switch( e.eventType ){
\r
930 this.triggered = false;
\r
934 // when the distance we moved is too small we skip this gesture
\r
935 // or we can be already in dragging
\r
936 if( e.distance < hammer.options.drag_min_distance && Detection.current.name !== this.name ) return;
\r
938 // we are dragging!
\r
939 Detection.current.name = this.name;
\r
941 // lock drag to axis?
\r
942 if( Detection.current.lastEvent.drag_locked_to_axis || ( hammer.options.drag_lock_to_axis && hammer.options.drag_lock_min_distance <= e.distance ) ){
\r
943 e.drag_locked_to_axis = true;
\r
945 last_direction = Detection.current.lastEvent.direction;
\r
946 if( e.drag_locked_to_axis && last_direction !== e.direction ){
\r
947 // keep direction on the axis that the drag gesture started on
\r
948 e.direction = Utils.isVertical( last_direction ) ?
\r
949 ( e.deltaY < 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN ) :
\r
950 ( e.deltaX < 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT );
\r
953 // first time, trigger dragstart event
\r
954 if( !this.triggered ){
\r
955 hammer.trigger( X.UI.Event.DRAG_START, e );
\r
956 this.triggered = true;
\r
959 // trigger normal event
\r
960 hammer.trigger( X.UI.Event.DRAG, e );
\r
962 // direction event, like dragdown
\r
964 e.direction === Hammer.DIRECTION_UP ?
\r
965 X.UI.Event.DRAG_UP :
\r
966 e.direction === Hammer.DIRECTION_DOWN ?
\r
967 X.UI.Event.DRAG_DOWN :
\r
968 e.direction === Hammer.DIRECTION_LEFT ?
\r
969 X.UI.Event.DRAG_LEFT :
\r
970 X.UI.Event.DRAG_RIGHT,
\r
974 // block the browser events
\r
976 ( hammer.options.drag_block_vertical && Utils.isVertical( e.direction ) ) ||
\r
977 ( hammer.options.drag_block_horizontal && !Utils.isVertical( e.direction ) )
\r
978 ) && e.preventDefault();
\r
983 this.triggered && hammer.trigger( X.UI.Event.DRAG_END, e );
\r
984 this.triggered = false;
\r
992 * User want to scale or rotate with 2 fingers
\r
993 * @events transform, transformstart, transformend, pinch, pinchin, pinchout, rotate
\r
995 Gestures.Transform = {
\r
996 name : 'transform',
\r
998 startID : X.UI.Event.TRANSFORM,
\r
999 endID : X.UI.Event.ROTATE,
\r
1001 // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
\r
1002 transform_min_scale : 0.01,
\r
1003 // rotation in degrees
\r
1004 transform_min_rotation : 1,
\r
1005 // prevent default browser behavior when two touches are on the screen
\r
1006 // but it makes the element a blocking element
\r
1007 // when you are using the transform gesture, it is a good practice to set this true
\r
1008 transform_always_block : false
\r
1010 triggered : false,
\r
1011 handler : function transformGesture( e, hammer ){
\r
1012 // current gesture isnt drag, but dragged is true
\r
1013 // this means an other gesture is busy. now call dragend
\r
1014 if( Detection.current.name !== this.name && this.triggered ){
\r
1015 hammer.trigger( X.UI.Event.TRANSFORM_END, e );
\r
1016 this.triggered = false;
\r
1020 // atleast multitouch
\r
1021 if( e.touches.length < 2 ) return;
\r
1023 // prevent default when two fingers are on the screen
\r
1024 hammer.options.transform_always_block && e.preventDefault();
\r
1026 switch(e.eventType) {
\r
1028 this.triggered = false;
\r
1032 var scale_threshold = ABS( 1 - e.scale ),
\r
1033 rotation_threshold = ABS( e.rotation );
\r
1035 // when the distance we moved is too small we skip this gesture
\r
1036 // or we can be already in dragging
\r
1037 if( scale_threshold < hammer.options.transform_min_scale && rotation_threshold < hammer.options.transform_min_rotation ) return;
\r
1039 // we are transforming!
\r
1040 Detection.current.name = this.name;
\r
1042 // first time, trigger dragstart event
\r
1043 if( !this.triggered ){
\r
1044 hammer.trigger( X.UI.Event.TRANSFORM_START, e );
\r
1045 this.triggered = true;
\r
1048 hammer.trigger( X.UI.Event.TRANSFORM, e );
\r
1049 // basic transform event
\r
1051 // trigger rotate event
\r
1052 hammer.options.transform_min_rotation < rotation_threshold && hammer.trigger( X.UI.Event.ROTATE, e );
\r
1054 // trigger pinch event
\r
1055 if( scale_threshold > hammer.options.transform_min_scale ){
\r
1056 hammer.trigger( X.UI.Event.PINCH, e );
\r
1057 hammer.trigger( e.scale < 1 ? X.UI.Event.PINCH_IN : X.UI.Event.PINCH_OUT, e );
\r
1062 // trigger dragend
\r
1063 this.triggered && hammer.trigger( X.UI.Event.TRANSFORM_END, e );
\r
1064 this.triggered = false;
\r
1072 * Called as first, tells the user has touched the screen
\r
1075 Gestures.Touch = {
\r
1077 index : -Infinity,
\r
1079 // call preventDefault at touchstart, and makes the element blocking by
\r
1080 // disabling the scrolling of the page, but it improves gestures like
\r
1081 // transforming and dragging.
\r
1082 // be careful with using this, it can be very annoying for users to be stuck
\r
1084 prevent_default : false,
\r
1086 // disable mouse events, so only touch (or pen!) input triggers events
\r
1087 prevent_mouseevents : false
\r
1089 handler : function touchGesture( e, hammer ){
\r
1090 if( hammer.options.prevent_mouseevents && e.pointerType === MOUSE ){
\r
1091 Detection.stopDetect();
\r
1095 hammer.options.prevent_default && e.preventDefault();
\r
1097 e.eventType === START && hammer.trigger( this.name, e );
\r
1103 * Called as last, tells the user has released the screen
\r
1106 Gestures.Release = {
\r
1109 handler : function releaseGesture( e, hammer ){
\r
1110 e.eventType === END && hammer.trigger( this.name, e );
\r
1114 })( Math, window, document );