1 /*! Hammer.JS - v1.0.5 - 2013-04-07
2 * http://eightmedia.github.com/hammer.js
4 * Copyright (c) 2013 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 || {});
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
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
35 contentZooming: 'none',
37 tapHighlightColor: 'rgba(0,0,0,0)'
40 // more settings are defined per gesture at gestures.js
44 Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
45 Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
47 // dont use mouseevents on mobile devices
48 Hammer.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
49 Hammer.NO_MOUSEEVENTS = Hammer.HAS_TOUCHEVENTS && navigator.userAgent.match(Hammer.MOBILE_REGEX);
51 // eventtypes per touchevent (start, move, end)
52 // are filled by Hammer.event.determineEventTypes on setup
53 Hammer.EVENT_TYPES = {};
56 Hammer.DIRECTION_DOWN = 'down';
57 Hammer.DIRECTION_LEFT = 'left';
58 Hammer.DIRECTION_UP = 'up';
59 Hammer.DIRECTION_RIGHT = 'right';
62 Hammer.POINTER_MOUSE = 'mouse';
63 Hammer.POINTER_TOUCH = 'touch';
64 Hammer.POINTER_PEN = 'pen';
66 // touch event defines
67 Hammer.EVENT_START = 'start';
68 Hammer.EVENT_MOVE = 'move';
69 Hammer.EVENT_END = 'end';
71 // hammer document where the base events are added at
72 Hammer.DOCUMENT = document;
77 // if the window events are set...
81 * setup events to detect gestures on the document
88 // find what eventtypes we add listeners to
89 Hammer.event.determineEventTypes();
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]);
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);
102 // Hammer is ready...!
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}
114 Hammer.Instance = function(element, options) {
117 // setup HammerJS window events and register all gestures
118 // this also sets up the default options
121 this.element = element;
123 // start/stop detection option
127 this.options = Hammer.utils.extend(
128 Hammer.utils.extend({}, Hammer.defaults),
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);
136 // start detection on touchstart
137 Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) {
139 Hammer.detection.startDetect(self, ev);
148 Hammer.Instance.prototype = {
150 * bind events to the instance
151 * @param {String} gesture
152 * @param {Function} handler
153 * @returns {Hammer.Instance}
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);
165 * unbind events to the instance
166 * @param {String} gesture
167 * @param {Function} handler
168 * @returns {Hammer.Instance}
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);
180 * trigger gesture event
181 * @param {String} gesture
182 * @param {Object} eventData
183 * @returns {Hammer.Instance}
185 trigger: function triggerEvent(gesture, eventData){
187 var event = Hammer.DOCUMENT.createEvent('Event');
188 event.initEvent(gesture, true, true);
189 event.gesture = eventData;
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;
198 element.dispatchEvent(event);
204 * enable of disable hammer.js detection
205 * @param {Boolean} state
206 * @returns {Hammer.Instance}
208 enable: function enable(state) {
209 this.enabled = state;
215 * this holds the last move event,
216 * used to fix empty touchend issue
217 * see the onTouch event for an explanation
220 var last_move_event = null;
224 * when the mouse is hold down, this is true
227 var enable_detect = false;
231 * when touch events have been fired, this is true
234 var touch_triggered = false;
239 * simple addEventListener
240 * @param {HTMLElement} element
241 * @param {String} type
242 * @param {Function} handler
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);
253 * touch events with mouse fallback
254 * @param {HTMLElement} element
255 * @param {String} eventType like Hammer.EVENT_MOVE
256 * @param {Function} handler
258 onTouch: function onTouch(element, eventType, handler) {
261 this.bindDom(element, Hammer.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
262 var sourceEventType = ev.type.toLowerCase();
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) {
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
275 enable_detect = true;
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;
284 // count the total touches on the screen
285 var count_touches = 0;
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
290 // update pointerevent
291 if(Hammer.HAS_POINTEREVENTS && eventType != Hammer.EVENT_END) {
292 count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
295 else if(sourceEventType.match(/touch/)) {
296 count_touches = ev.touches.length;
299 else if(!touch_triggered) {
300 count_touches = sourceEventType.match(/up/) ? 0 : 1;
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;
308 // no touches, force the end event
309 else if(!count_touches) {
310 eventType = Hammer.EVENT_END;
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;
318 // store the last move event
320 last_move_event = ev;
323 // trigger the handler
324 handler.call(Hammer.detection, self.collectEventData(element, eventType, ev));
326 // remove pointerevent from list
327 if(Hammer.HAS_POINTEREVENTS && eventType == Hammer.EVENT_END) {
328 count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
332 //debug(sourceEventType +" "+ eventType);
334 // on the end we reset everything
336 last_move_event = null;
337 enable_detect = false;
338 touch_triggered = false;
339 Hammer.PointerEvent.reset();
346 * we have different events for each device/browser
347 * determine what we need and set them in the Hammer.EVENT_TYPES constant
349 determineEventTypes: function determineEventTypes() {
350 // determine the eventtype we want to set
353 // pointerEvents magic
354 if(Hammer.HAS_POINTEREVENTS) {
355 types = Hammer.PointerEvent.getEvents();
357 // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
358 else if(Hammer.NO_MOUSEEVENTS) {
362 'touchend touchcancel'];
364 // for non pointer events browsers and mixed browsers,
365 // like chrome on windows8 touch laptop
368 'touchstart mousedown',
369 'touchmove mousemove',
370 'touchend touchcancel mouseup'];
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];
380 * create touchlist depending on the event
382 * @param {String} eventType used by the fakemultitouch plugin
384 getTouchList: function getTouchList(ev/*, eventType*/) {
385 // get the fake pointerEvent touchlist
386 if(Hammer.HAS_POINTEREVENTS) {
387 return Hammer.PointerEvent.getTouchList();
390 else if(ev.touches) {
393 // make fake touchlist from mouse position
406 * collect event data for Hammer js
407 * @param {HTMLElement} element
408 * @param {String} eventType like Hammer.EVENT_MOVE
409 * @param {Object} eventData
411 collectEventData: function collectEventData(element, eventType, ev) {
412 var touches = this.getTouchList(ev, eventType);
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;
421 center : Hammer.utils.getCenter(touches),
422 timeStamp : new Date().getTime(),
425 eventType : eventType,
426 pointerType : pointerType,
430 * prevent the browser default actions
431 * mostly used to disable scrolling of the browser
433 preventDefault: function() {
434 if(this.srcEvent.preventManipulation) {
435 this.srcEvent.preventManipulation();
438 if(this.srcEvent.preventDefault) {
439 this.srcEvent.preventDefault();
444 * stop bubbling the event up to its parents
446 stopPropagation: function() {
447 this.srcEvent.stopPropagation();
451 * immediately stop gesture detection
452 * might be useful after a swipe was detected
455 stopDetect: function() {
456 return Hammer.detection.stopDetect();
462 Hammer.PointerEvent = {
470 * get a list of pointers
471 * @returns {Array} touchlist
473 getTouchList: function() {
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]);
485 * update the position of a pointer
486 * @param {String} type Hammer.EVENT_END
487 * @param {Object} pointerEvent
489 updatePointer: function(type, pointerEvent) {
490 if(type == Hammer.EVENT_END) {
494 pointerEvent.identifier = pointerEvent.pointerId;
495 this.pointers[pointerEvent.pointerId] = pointerEvent;
498 return Object.keys(this.pointers).length;
502 * check if ev matches pointertype
503 * @param {String} pointerType Hammer.POINTER_MOUSE
504 * @param {PointerEvent} ev
506 matchType: function(pointerType, ev) {
507 if(!ev.pointerType) {
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];
522 getEvents: function() {
524 'pointerdown MSPointerDown',
525 'pointermove MSPointerMove',
526 'pointerup pointercancel MSPointerUp MSPointerCancel'
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
548 extend: function extend(dest, src, merge) {
549 for (var key in src) {
550 if(dest[key] !== undefined && merge) {
553 dest[key] = src[key];
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
566 hasParent: function(node, parent) {
571 node = node.parentNode;
578 * get the center of all the touches
579 * @param {Array} touches
580 * @returns {Object} center
582 getCenter: function getCenter(touches) {
583 var valuesX = [], valuesY = [];
585 for(var t= 0,len=touches.length; t<len; t++) {
586 valuesX.push(touches[t].pageX);
587 valuesY.push(touches[t].pageY);
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)
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
604 getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
606 x: Math.abs(delta_x / delta_time) || 0,
607 y: Math.abs(delta_y / delta_time) || 0
613 * calculate the angle between two coordinates
614 * @param {Touch} touch1
615 * @param {Touch} touch2
616 * @returns {Number} angle
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;
626 * angle to direction define
627 * @param {Touch} touch1
628 * @param {Touch} touch2
629 * @returns {String} direction constant, like Hammer.DIRECTION_LEFT
631 getDirection: function getDirection(touch1, touch2) {
632 var x = Math.abs(touch1.pageX - touch2.pageX),
633 y = Math.abs(touch1.pageY - touch2.pageY);
636 return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
639 return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN;
645 * calculate the distance between two touches
646 * @param {Touch} touch1
647 * @param {Touch} touch2
648 * @returns {Number} distance
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));
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
662 * @returns {Number} scale
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]);
675 * calculate the rotation degrees between two touchLists (fingers)
676 * @param {Array} start
678 * @returns {Number} rotation
680 getRotation: function getRotation(start, end) {
682 if(start.length >= 2 && end.length >= 2) {
683 return this.getAngle(end[1], end[0]) -
684 this.getAngle(start[1], start[0]);
691 * boolean if the direction is vertical
692 * @param {String} direction
693 * @returns {Boolean} is_vertical
695 isVertical: function isVertical(direction) {
696 return (direction == Hammer.DIRECTION_UP || direction == Hammer.DIRECTION_DOWN);
701 * stop browser default behavior with css props
702 * @param {HtmlElement} element
703 * @param {Object} css_props
705 stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) {
707 vendors = ['webkit','khtml','moz','ms','o',''];
709 if(!css_props || !element.style) {
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)) {
719 // vender prefix at the property
721 prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1);
725 element.style[prop] = css_props[p];
730 // also the disable onselectstart
731 if(css_props.userSelect == 'none') {
732 element.onselectstart = function() {
740 // contains all registred Hammer.gestures in the correct order
743 // data of the current Hammer.gesture detection session
746 // the previous Hammer.gesture session data
747 // is a full clone of the previous gesture.current object
750 // when this becomes true, no gestures are fired
755 * start Hammer.gesture detection
756 * @param {Hammer.Instance} inst
757 * @param {Object} eventData
759 startDetect: function startDetect(inst, eventData) {
760 // already busy with a Hammer.gesture detection on an element
765 this.stopped = false;
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
774 this.detect(eventData);
779 * Hammer.gesture detection
780 * @param {Object} eventData
781 * @param {Object} eventData
783 detect: function detect(eventData) {
784 if(!this.current || this.stopped) {
788 // extend event data with calculations about scale, distance etc
789 eventData = this.extendEventData(eventData);
792 var inst_options = this.current.inst.options;
794 // call Hammer.gesture handlers
795 for(var g=0,len=this.gestures.length; g<len; g++) {
796 var gesture = this.gestures[g];
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) {
808 // store as previous event event
810 this.current.lastEvent = eventData;
813 // endevent, but not the last touch, so dont stop
814 if(eventData.eventType == Hammer.EVENT_END && !eventData.touches.length-1) {
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
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);
841 * extend eventData for Hammer.gestures
843 * @returns {Object} ev
845 extendEventData: function extendEventData(ev) {
846 var startEv = this.current.startEvent;
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]));
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);
865 Hammer.utils.extend(ev, {
866 deltaTime : delta_time,
871 velocityX : velocity.x,
872 velocityY : velocity.y,
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),
878 scale : Hammer.utils.getScale(startEv.touches, ev.touches),
879 rotation : Hammer.utils.getRotation(startEv.touches, ev.touches),
889 * register new gesture
890 * @param {Object} gesture object, see gestures.js for documentation
891 * @returns {Array} gestures
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;
900 // extend Hammer default options with the Hammer.gesture options
901 Hammer.utils.extend(Hammer.defaults, options, true);
904 gesture.index = gesture.index || 1000;
906 // add Hammer.gesture to the list
907 this.gestures.push(gesture);
909 // sort the list by index
910 this.gestures.sort(function(a, b) {
911 if (a.index < b.index) {
914 if (a.index > b.index) {
920 return this.gestures;
925 Hammer.gestures = Hammer.gestures || {};
929 * ==============================
932 * --------------------
933 * The object structure of a gesture:
935 * { name: 'mygesture',
938 * mygesture_option: true
940 * handler: function(type, ev, inst) {
941 * // trigger gesture event
942 * inst.trigger(this.name, ev);
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.
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.
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).
960 * @param {Function} handler
961 * this handles the gesture detection of your custom gesture and receives the
962 * following arguments:
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
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
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
1003 * @param {Hammer.Instance} inst
1004 * the instance we do the detection for
1007 * @param {Object} startEvent
1008 * contains the properties of the first gesture detection in this session.
1009 * Used for calculations about timing, distance, etc.
1012 * @param {Object} lastEvent
1013 * contains all the properties of the last gesture detect in this session.
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
1020 * options that have been set by the instance can be received by calling inst.options
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
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
1036 * Touch stays at the same place for x time
1039 Hammer.gestures.Hold = {
1047 handler: function holdGesture(ev, inst) {
1048 switch(ev.eventType) {
1049 case Hammer.EVENT_START:
1050 // clear any running timers
1051 clearTimeout(this.timer);
1053 // set the gesture so we can check in the timeout if it still is
1054 Hammer.detection.current.name = this.name;
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);
1062 }, inst.options.hold_timeout);
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);
1072 case Hammer.EVENT_END:
1073 clearTimeout(this.timer);
1082 * Quick touch at a place or double at the same place
1083 * @events tap, doubletap
1085 Hammer.gestures.Tap = {
1089 tap_max_touchtime : 250,
1090 tap_max_distance : 10,
1092 doubletap_distance : 20,
1093 doubletap_interval : 300
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;
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) {
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;
1117 if(!did_doubletap || inst.options.tap_always) {
1118 Hammer.detection.current.name = 'tap';
1119 inst.trigger(Hammer.detection.current.name, ev);
1128 * triggers swipe events when the end velocity is above the threshold
1129 * @events swipe, swipeleft, swiperight, swipeup, swipedown
1131 Hammer.gestures.Swipe = {
1135 // set 0 for unlimited, but this can conflict with transform
1136 swipe_max_touches : 1,
1137 swipe_velocity : 0.7
1139 handler: function swipeGesture(ev, inst) {
1140 if(ev.eventType == Hammer.EVENT_END) {
1142 if(inst.options.swipe_max_touches > 0 &&
1143 ev.touches.length > inst.options.swipe_max_touches) {
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);
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
1167 Hammer.gestures.Drag = {
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
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;
1197 if(inst.options.drag_max_touches > 0 &&
1198 ev.touches.length > inst.options.drag_max_touches) {
1202 switch(ev.eventType) {
1203 case Hammer.EVENT_START:
1204 this.triggered = false;
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) {
1216 Hammer.detection.current.name = this.name;
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;
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;
1229 ev.direction = (ev.deltaX < 0) ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
1233 // first time, trigger dragstart event
1234 if(!this.triggered) {
1235 inst.trigger(this.name +'start', ev);
1236 this.triggered = true;
1239 // trigger normal event
1240 inst.trigger(this.name, ev);
1242 // direction event, like dragdown
1243 inst.trigger(this.name + ev.direction, ev);
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();
1252 case Hammer.EVENT_END:
1254 if(this.triggered) {
1255 inst.trigger(this.name +'end', ev);
1258 this.triggered = false;
1267 * User want to scale or rotate with 2 fingers
1268 * @events transform, pinch, pinchin, pinchout, rotate
1270 Hammer.gestures.Transform = {
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
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;
1293 // atleast multitouch
1294 if(ev.touches.length < 2) {
1298 // prevent default when two fingers are on the screen
1299 if(inst.options.transform_always_block) {
1300 ev.preventDefault();
1303 switch(ev.eventType) {
1304 case Hammer.EVENT_START:
1305 this.triggered = false;
1308 case Hammer.EVENT_MOVE:
1309 var scale_threshold = Math.abs(1-ev.scale);
1310 var rotation_threshold = Math.abs(ev.rotation);
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) {
1319 // we are transforming!
1320 Hammer.detection.current.name = this.name;
1322 // first time, trigger dragstart event
1323 if(!this.triggered) {
1324 inst.trigger(this.name +'start', ev);
1325 this.triggered = true;
1328 inst.trigger(this.name, ev); // basic transform event
1330 // trigger rotate event
1331 if(rotation_threshold > inst.options.transform_min_rotation) {
1332 inst.trigger('rotate', ev);
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);
1342 case Hammer.EVENT_END:
1344 if(this.triggered) {
1345 inst.trigger(this.name +'end', ev);
1348 this.triggered = false;
1357 * Called as first, tells the user has touched the screen
1360 Hammer.gestures.Touch = {
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
1369 prevent_default: false,
1371 // disable mouse events, so only touch (or pen!) input triggers events
1372 prevent_mouseevents: false
1374 handler: function touchGesture(ev, inst) {
1375 if(inst.options.prevent_mouseevents && ev.pointerType == Hammer.POINTER_MOUSE) {
1380 if(inst.options.prevent_default) {
1381 ev.preventDefault();
1384 if(ev.eventType == Hammer.EVENT_START) {
1385 inst.trigger(this.name, ev);
1393 * Called as last, tells the user has released the screen
1396 Hammer.gestures.Release = {
1399 handler: function releaseGesture(ev, inst) {
1400 if(ev.eventType == Hammer.EVENT_END) {
1401 inst.trigger(this.name, ev);
1407 if(typeof module === 'object' && typeof module.exports === 'object'){
1408 module.exports = Hammer;
1410 // just window export
1412 window.Hammer = Hammer;
1414 // requireJS module definition
1415 if(typeof window.define === 'function' && window.define.amd) {
1416 window.define('hammer', [], function() {
1424 angular.module('angular-gestures', []);
1427 * Inspired by AngularJS' implementation of "click dblclick mousedown..."
1429 * This ties in the Hammer 1.0.0 events to attributes like:
1431 * hm-tap="add_something()" hm-swipe="remove_something()"
1433 * and also has support for Hammer options with:
1435 * hm-tap-opts="{hold: false}"
1437 * or any other of the "hm-event" listed underneath.
1440 hmDoubleTap : 'doubletap',
1441 hmDragstart : 'dragstart',
1443 hmDragUp : 'dragup',
1444 hmDragDown : 'dragdown',
1445 hmDragLeft : 'dragleft',
1446 hmDragRight : 'dragright',
1447 hmDragend : 'dragend',
1450 hmPinchIn : 'pinchin',
1451 hmPinchOut : 'pinchout',
1452 hmRelease : 'release',
1453 hmRotate : 'rotate',
1455 hmSwipeUp : 'swipeup',
1456 hmSwipeDown : 'swipedown',
1457 hmSwipeLeft : 'swipeleft',
1458 hmSwipeRight : 'swiperight',
1461 hmTransformstart : 'transformstart',
1462 hmTransform : 'transform',
1463 hmTransformend : 'transformend'
1466 var VERBOSE = false;
1468 angular.forEach(HGESTURES, function(eventName, directiveName) {
1469 angular.module('angular-gestures').directive(
1471 ['$parse', '$log', '$timeout', function($parse, $log, $timeout) {
1472 return function(scope, element, attr) {
1473 var hammertime, handler;
1474 attr.$observe(directiveName, function(value) {
1475 var fn = $parse(value);
1476 var opts = $parse(attr[directiveName + 'Opts'])
1478 hammertime = new Hammer(element[0], opts);
1479 handler = function(event) {
1481 $log.debug('angular-gestures: %s',
1484 $timeout(function() {
1485 fn(scope, { $event : event });
1488 hammertime.on(eventName, handler);
1490 scope.$on('$destroy', function() {
1491 hammertime.off(eventName, handler);