1 /*! Hammer.JS - v1.0.5 - 2013-04-07
\r
2 * http://eightmedia.github.com/hammer.js
\r
4 * Copyright (c) 2013 Jorik Tangelder <j.tangelder@gmail.com>;
\r
5 * Licensed under the MIT license */
\r
7 (function(window, undefined) {
\r
12 * use this to create instances
\r
13 * @param {HTMLElement} element
\r
14 * @param {Object} options
\r
15 * @returns {Hammer.Instance}
\r
18 var Hammer = function(element, options) {
\r
19 return new Hammer.Instance(element, options || {});
\r
24 // add styles and attributes to the element to prevent the browser from doing
\r
25 // its native behavior. this doesnt prevent the scrolling, but cancels
\r
26 // the contextmenu, tap highlighting etc
\r
27 // set to false to disable this
\r
28 stop_browser_behavior: {
\r
29 // this also triggers onselectstart=false for IE
\r
31 // this makes the element blocking in IE10 >, you could experiment with the value
\r
32 // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241
\r
33 touchAction: 'none',
\r
34 touchCallout: 'none',
\r
35 contentZooming: 'none',
\r
37 tapHighlightColor: 'rgba(0,0,0,0)'
\r
40 // more settings are defined per gesture at gestures.js
\r
43 // detect touchevents
\r
44 Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
\r
45 Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
\r
47 // dont use mouseevents on mobile devices
\r
48 Hammer.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
\r
49 Hammer.NO_MOUSEEVENTS = Hammer.HAS_TOUCHEVENTS && navigator.userAgent.match(Hammer.MOBILE_REGEX);
\r
51 // eventtypes per touchevent (start, move, end)
\r
52 // are filled by Hammer.event.determineEventTypes on setup
\r
53 Hammer.EVENT_TYPES = {};
\r
55 // direction defines
\r
56 Hammer.DIRECTION_DOWN = 'down';
\r
57 Hammer.DIRECTION_LEFT = 'left';
\r
58 Hammer.DIRECTION_UP = 'up';
\r
59 Hammer.DIRECTION_RIGHT = 'right';
\r
62 Hammer.POINTER_MOUSE = 'mouse';
\r
63 Hammer.POINTER_TOUCH = 'touch';
\r
64 Hammer.POINTER_PEN = 'pen';
\r
66 // touch event defines
\r
67 Hammer.EVENT_START = 'start';
\r
68 Hammer.EVENT_MOVE = 'move';
\r
69 Hammer.EVENT_END = 'end';
\r
71 // hammer document where the base events are added at
\r
72 Hammer.DOCUMENT = document;
\r
74 // plugins namespace
\r
75 Hammer.plugins = {};
\r
77 // if the window events are set...
\r
78 Hammer.READY = false;
\r
81 * setup events to detect gestures on the document
\r
88 // find what eventtypes we add listeners to
\r
89 Hammer.event.determineEventTypes();
\r
91 // Register all gestures inside Hammer.gestures
\r
92 for(var name in Hammer.gestures) {
\r
93 if(Hammer.gestures.hasOwnProperty(name)) {
\r
94 Hammer.detection.register(Hammer.gestures[name]);
\r
98 // Add touch events on the document
\r
99 Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_MOVE, Hammer.detection.detect);
\r
100 Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_END, Hammer.detection.detect);
\r
102 // Hammer is ready...!
\r
103 Hammer.READY = true;
\r
107 * create new hammer instance
\r
108 * all methods should return the instance itself, so it is chainable.
\r
109 * @param {HTMLElement} element
\r
110 * @param {Object} [options={}]
\r
111 * @returns {Hammer.Instance}
\r
114 Hammer.Instance = function(element, options) {
\r
117 // setup HammerJS window events and register all gestures
\r
118 // this also sets up the default options
\r
121 this.element = element;
\r
123 // start/stop detection option
\r
124 this.enabled = true;
\r
127 this.options = Hammer.utils.extend(
\r
128 Hammer.utils.extend({}, Hammer.defaults),
\r
131 // add some css to the element to prevent the browser from doing its native behavoir
\r
132 if(this.options.stop_browser_behavior) {
\r
133 Hammer.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
\r
136 // start detection on touchstart
\r
137 Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) {
\r
139 Hammer.detection.startDetect(self, ev);
\r
148 Hammer.Instance.prototype = {
\r
150 * bind events to the instance
\r
151 * @param {String} gesture
\r
152 * @param {Function} handler
\r
153 * @returns {Hammer.Instance}
\r
155 on: function onEvent(gesture, handler){
\r
156 var gestures = gesture.split(' ');
\r
157 for(var t=0; t<gestures.length; t++) {
\r
158 this.element.addEventListener(gestures[t], handler, false);
\r
165 * unbind events to the instance
\r
166 * @param {String} gesture
\r
167 * @param {Function} handler
\r
168 * @returns {Hammer.Instance}
\r
170 off: function offEvent(gesture, handler){
\r
171 var gestures = gesture.split(' ');
\r
172 for(var t=0; t<gestures.length; t++) {
\r
173 this.element.removeEventListener(gestures[t], handler, false);
\r
180 * trigger gesture event
\r
181 * @param {String} gesture
\r
182 * @param {Object} eventData
\r
183 * @returns {Hammer.Instance}
\r
185 trigger: function triggerEvent(gesture, eventData){
\r
186 // create DOM event
\r
187 var event = Hammer.DOCUMENT.createEvent('Event');
\r
188 event.initEvent(gesture, true, true);
\r
189 event.gesture = eventData;
\r
191 // trigger on the target if it is in the instance element,
\r
192 // this is for event delegation tricks
\r
193 var element = this.element;
\r
194 if(Hammer.utils.hasParent(eventData.target, element)) {
\r
195 element = eventData.target;
\r
198 element.dispatchEvent(event);
\r
204 * enable of disable hammer.js detection
\r
205 * @param {Boolean} state
\r
206 * @returns {Hammer.Instance}
\r
208 enable: function enable(state) {
\r
209 this.enabled = state;
\r
215 * this holds the last move event,
\r
216 * used to fix empty touchend issue
\r
217 * see the onTouch event for an explanation
\r
220 var last_move_event = null;
\r
224 * when the mouse is hold down, this is true
\r
227 var enable_detect = false;
\r
231 * when touch events have been fired, this is true
\r
234 var touch_triggered = false;
\r
239 * simple addEventListener
\r
240 * @param {HTMLElement} element
\r
241 * @param {String} type
\r
242 * @param {Function} handler
\r
244 bindDom: function(element, type, handler) {
\r
245 var types = type.split(' ');
\r
246 for(var t=0; t<types.length; t++) {
\r
247 element.addEventListener(types[t], handler, false);
\r
253 * touch events with mouse fallback
\r
254 * @param {HTMLElement} element
\r
255 * @param {String} eventType like Hammer.EVENT_MOVE
\r
256 * @param {Function} handler
\r
258 onTouch: function onTouch(element, eventType, handler) {
\r
261 this.bindDom(element, Hammer.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
\r
262 var sourceEventType = ev.type.toLowerCase();
\r
264 // onmouseup, but when touchend has been fired we do nothing.
\r
265 // this is for touchdevices which also fire a mouseup on touchend
\r
266 if(sourceEventType.match(/mouse/) && touch_triggered) {
\r
270 // mousebutton must be down or a touch event
\r
271 else if( sourceEventType.match(/touch/) || // touch events are always on screen
\r
272 sourceEventType.match(/pointerdown/) || // pointerevents touch
\r
273 (sourceEventType.match(/mouse/) && ev.which === 1) // mouse is pressed
\r
275 enable_detect = true;
\r
278 // we are in a touch event, set the touch triggered bool to true,
\r
279 // this for the conflicts that may occur on ios and android
\r
280 if(sourceEventType.match(/touch|pointer/)) {
\r
281 touch_triggered = true;
\r
284 // count the total touches on the screen
\r
285 var count_touches = 0;
\r
287 // when touch has been triggered in this detection session
\r
288 // and we are now handling a mouse event, we stop that to prevent conflicts
\r
289 if(enable_detect) {
\r
290 // update pointerevent
\r
291 if(Hammer.HAS_POINTEREVENTS && eventType != Hammer.EVENT_END) {
\r
292 count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
\r
295 else if(sourceEventType.match(/touch/)) {
\r
296 count_touches = ev.touches.length;
\r
299 else if(!touch_triggered) {
\r
300 count_touches = sourceEventType.match(/up/) ? 0 : 1;
\r
303 // if we are in a end event, but when we remove one touch and
\r
304 // we still have enough, set eventType to move
\r
305 if(count_touches > 0 && eventType == Hammer.EVENT_END) {
\r
306 eventType = Hammer.EVENT_MOVE;
\r
308 // no touches, force the end event
\r
309 else if(!count_touches) {
\r
310 eventType = Hammer.EVENT_END;
\r
313 // because touchend has no touches, and we often want to use these in our gestures,
\r
314 // we send the last move event as our eventData in touchend
\r
315 if(!count_touches && last_move_event !== null) {
\r
316 ev = last_move_event;
\r
318 // store the last move event
\r
320 last_move_event = ev;
\r
323 // trigger the handler
\r
324 handler.call(Hammer.detection, self.collectEventData(element, eventType, ev));
\r
326 // remove pointerevent from list
\r
327 if(Hammer.HAS_POINTEREVENTS && eventType == Hammer.EVENT_END) {
\r
328 count_touches = Hammer.PointerEvent.updatePointer(eventType, ev);
\r
332 //debug(sourceEventType +" "+ eventType);
\r
334 // on the end we reset everything
\r
335 if(!count_touches) {
\r
336 last_move_event = null;
\r
337 enable_detect = false;
\r
338 touch_triggered = false;
\r
339 Hammer.PointerEvent.reset();
\r
346 * we have different events for each device/browser
\r
347 * determine what we need and set them in the Hammer.EVENT_TYPES constant
\r
349 determineEventTypes: function determineEventTypes() {
\r
350 // determine the eventtype we want to set
\r
353 // pointerEvents magic
\r
354 if(Hammer.HAS_POINTEREVENTS) {
\r
355 types = Hammer.PointerEvent.getEvents();
\r
357 // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
\r
358 else if(Hammer.NO_MOUSEEVENTS) {
\r
362 'touchend touchcancel'];
\r
364 // for non pointer events browsers and mixed browsers,
\r
365 // like chrome on windows8 touch laptop
\r
368 'touchstart mousedown',
\r
369 'touchmove mousemove',
\r
370 'touchend touchcancel mouseup'];
\r
373 Hammer.EVENT_TYPES[Hammer.EVENT_START] = types[0];
\r
374 Hammer.EVENT_TYPES[Hammer.EVENT_MOVE] = types[1];
\r
375 Hammer.EVENT_TYPES[Hammer.EVENT_END] = types[2];
\r
380 * create touchlist depending on the event
\r
381 * @param {Object} ev
\r
382 * @param {String} eventType used by the fakemultitouch plugin
\r
384 getTouchList: function getTouchList(ev/*, eventType*/) {
\r
385 // get the fake pointerEvent touchlist
\r
386 if(Hammer.HAS_POINTEREVENTS) {
\r
387 return Hammer.PointerEvent.getTouchList();
\r
389 // get the touchlist
\r
390 else if(ev.touches) {
\r
393 // make fake touchlist from mouse position
\r
406 * collect event data for Hammer js
\r
407 * @param {HTMLElement} element
\r
408 * @param {String} eventType like Hammer.EVENT_MOVE
\r
409 * @param {Object} eventData
\r
411 collectEventData: function collectEventData(element, eventType, ev) {
\r
412 var touches = this.getTouchList(ev, eventType);
\r
414 // find out pointerType
\r
415 var pointerType = Hammer.POINTER_TOUCH;
\r
416 if(ev.type.match(/mouse/) || Hammer.PointerEvent.matchType(Hammer.POINTER_MOUSE, ev)) {
\r
417 pointerType = Hammer.POINTER_MOUSE;
\r
421 center : Hammer.utils.getCenter(touches),
\r
422 timeStamp : new Date().getTime(),
\r
423 target : ev.target,
\r
425 eventType : eventType,
\r
426 pointerType : pointerType,
\r
430 * prevent the browser default actions
\r
431 * mostly used to disable scrolling of the browser
\r
433 preventDefault: function() {
\r
434 if(this.srcEvent.preventManipulation) {
\r
435 this.srcEvent.preventManipulation();
\r
438 if(this.srcEvent.preventDefault) {
\r
439 this.srcEvent.preventDefault();
\r
444 * stop bubbling the event up to its parents
\r
446 stopPropagation: function() {
\r
447 this.srcEvent.stopPropagation();
\r
451 * immediately stop gesture detection
\r
452 * might be useful after a swipe was detected
\r
455 stopDetect: function() {
\r
456 return Hammer.detection.stopDetect();
\r
462 Hammer.PointerEvent = {
\r
464 * holds all pointers
\r
470 * get a list of pointers
\r
471 * @returns {Array} touchlist
\r
473 getTouchList: function() {
\r
475 var touchlist = [];
\r
477 // we can use forEach since pointerEvents only is in IE10
\r
478 Object.keys(self.pointers).sort().forEach(function(id) {
\r
479 touchlist.push(self.pointers[id]);
\r
485 * update the position of a pointer
\r
486 * @param {String} type Hammer.EVENT_END
\r
487 * @param {Object} pointerEvent
\r
489 updatePointer: function(type, pointerEvent) {
\r
490 if(type == Hammer.EVENT_END) {
\r
491 this.pointers = {};
\r
494 pointerEvent.identifier = pointerEvent.pointerId;
\r
495 this.pointers[pointerEvent.pointerId] = pointerEvent;
\r
498 return Object.keys(this.pointers).length;
\r
502 * check if ev matches pointertype
\r
503 * @param {String} pointerType Hammer.POINTER_MOUSE
\r
504 * @param {PointerEvent} ev
\r
506 matchType: function(pointerType, ev) {
\r
507 if(!ev.pointerType) {
\r
512 types[Hammer.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == Hammer.POINTER_MOUSE);
\r
513 types[Hammer.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == Hammer.POINTER_TOUCH);
\r
514 types[Hammer.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == Hammer.POINTER_PEN);
\r
515 return types[pointerType];
\r
522 getEvents: function() {
\r
524 'pointerdown MSPointerDown',
\r
525 'pointermove MSPointerMove',
\r
526 'pointerup pointercancel MSPointerUp MSPointerCancel'
\r
533 reset: function() {
\r
534 this.pointers = {};
\r
542 * also used for cloning when dest is an empty object
\r
543 * @param {Object} dest
\r
544 * @param {Object} src
\r
545 * @parm {Boolean} merge do a merge
\r
546 * @returns {Object} dest
\r
548 extend: function extend(dest, src, merge) {
\r
549 for (var key in src) {
\r
550 if(dest[key] !== undefined && merge) {
\r
553 dest[key] = src[key];
\r
560 * find if a node is in the given parent
\r
561 * used for event delegation tricks
\r
562 * @param {HTMLElement} node
\r
563 * @param {HTMLElement} parent
\r
564 * @returns {boolean} has_parent
\r
566 hasParent: function(node, parent) {
\r
568 if(node == parent) {
\r
571 node = node.parentNode;
\r
578 * get the center of all the touches
\r
579 * @param {Array} touches
\r
580 * @returns {Object} center
\r
582 getCenter: function getCenter(touches) {
\r
583 var valuesX = [], valuesY = [];
\r
585 for(var t= 0,len=touches.length; t<len; t++) {
\r
586 valuesX.push(touches[t].pageX);
\r
587 valuesY.push(touches[t].pageY);
\r
591 pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
\r
592 pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
\r
598 * calculate the velocity between two points
\r
599 * @param {Number} delta_time
\r
600 * @param {Number} delta_x
\r
601 * @param {Number} delta_y
\r
602 * @returns {Object} velocity
\r
604 getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
\r
606 x: Math.abs(delta_x / delta_time) || 0,
\r
607 y: Math.abs(delta_y / delta_time) || 0
\r
613 * calculate the angle between two coordinates
\r
614 * @param {Touch} touch1
\r
615 * @param {Touch} touch2
\r
616 * @returns {Number} angle
\r
618 getAngle: function getAngle(touch1, touch2) {
\r
619 var y = touch2.pageY - touch1.pageY,
\r
620 x = touch2.pageX - touch1.pageX;
\r
621 return Math.atan2(y, x) * 180 / Math.PI;
\r
626 * angle to direction define
\r
627 * @param {Touch} touch1
\r
628 * @param {Touch} touch2
\r
629 * @returns {String} direction constant, like Hammer.DIRECTION_LEFT
\r
631 getDirection: function getDirection(touch1, touch2) {
\r
632 var x = Math.abs(touch1.pageX - touch2.pageX),
\r
633 y = Math.abs(touch1.pageY - touch2.pageY);
\r
636 return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
\r
639 return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN;
\r
645 * calculate the distance between two touches
\r
646 * @param {Touch} touch1
\r
647 * @param {Touch} touch2
\r
648 * @returns {Number} distance
\r
650 getDistance: function getDistance(touch1, touch2) {
\r
651 var x = touch2.pageX - touch1.pageX,
\r
652 y = touch2.pageY - touch1.pageY;
\r
653 return Math.sqrt((x*x) + (y*y));
\r
658 * calculate the scale factor between two touchLists (fingers)
\r
659 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
\r
660 * @param {Array} start
\r
661 * @param {Array} end
\r
662 * @returns {Number} scale
\r
664 getScale: function getScale(start, end) {
\r
665 // need two fingers...
\r
666 if(start.length >= 2 && end.length >= 2) {
\r
667 return this.getDistance(end[0], end[1]) /
\r
668 this.getDistance(start[0], start[1]);
\r
675 * calculate the rotation degrees between two touchLists (fingers)
\r
676 * @param {Array} start
\r
677 * @param {Array} end
\r
678 * @returns {Number} rotation
\r
680 getRotation: function getRotation(start, end) {
\r
681 // need two fingers
\r
682 if(start.length >= 2 && end.length >= 2) {
\r
683 return this.getAngle(end[1], end[0]) -
\r
684 this.getAngle(start[1], start[0]);
\r
691 * boolean if the direction is vertical
\r
692 * @param {String} direction
\r
693 * @returns {Boolean} is_vertical
\r
695 isVertical: function isVertical(direction) {
\r
696 return (direction == Hammer.DIRECTION_UP || direction == Hammer.DIRECTION_DOWN);
\r
701 * stop browser default behavior with css props
\r
702 * @param {HtmlElement} element
\r
703 * @param {Object} css_props
\r
705 stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) {
\r
707 vendors = ['webkit','khtml','moz','ms','o',''];
\r
709 if(!css_props || !element.style) {
\r
713 // with css properties for modern browsers
\r
714 for(var i = 0; i < vendors.length; i++) {
\r
715 for(var p in css_props) {
\r
716 if(css_props.hasOwnProperty(p)) {
\r
719 // vender prefix at the property
\r
721 prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1);
\r
725 element.style[prop] = css_props[p];
\r
730 // also the disable onselectstart
\r
731 if(css_props.userSelect == 'none') {
\r
732 element.onselectstart = function() {
\r
739 Hammer.detection = {
\r
740 // contains all registred Hammer.gestures in the correct order
\r
743 // data of the current Hammer.gesture detection session
\r
746 // the previous Hammer.gesture session data
\r
747 // is a full clone of the previous gesture.current object
\r
750 // when this becomes true, no gestures are fired
\r
755 * start Hammer.gesture detection
\r
756 * @param {Hammer.Instance} inst
\r
757 * @param {Object} eventData
\r
759 startDetect: function startDetect(inst, eventData) {
\r
760 // already busy with a Hammer.gesture detection on an element
\r
765 this.stopped = false;
\r
768 inst : inst, // reference to HammerInstance we're working for
\r
769 startEvent : Hammer.utils.extend({}, eventData), // start eventData for distances, timing etc
\r
770 lastEvent : false, // last eventData
\r
771 name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc
\r
774 this.detect(eventData);
\r
779 * Hammer.gesture detection
\r
780 * @param {Object} eventData
\r
781 * @param {Object} eventData
\r
783 detect: function detect(eventData) {
\r
784 if(!this.current || this.stopped) {
\r
788 // extend event data with calculations about scale, distance etc
\r
789 eventData = this.extendEventData(eventData);
\r
791 // instance options
\r
792 var inst_options = this.current.inst.options;
\r
794 // call Hammer.gesture handlers
\r
795 for(var g=0,len=this.gestures.length; g<len; g++) {
\r
796 var gesture = this.gestures[g];
\r
798 // only when the instance options have enabled this gesture
\r
799 if(!this.stopped && inst_options[gesture.name] !== false) {
\r
800 // if a handler returns false, we stop with the detection
\r
801 if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
\r
808 // store as previous event event
\r
810 this.current.lastEvent = eventData;
\r
813 // endevent, but not the last touch, so dont stop
\r
814 if(eventData.eventType == Hammer.EVENT_END && !eventData.touches.length-1) {
\r
823 * clear the Hammer.gesture vars
\r
824 * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
\r
825 * to stop other Hammer.gestures from being fired
\r
827 stopDetect: function stopDetect() {
\r
828 // clone current data to the store as the previous gesture
\r
829 // used for the double tap gesture, since this is an other gesture detect session
\r
830 this.previous = Hammer.utils.extend({}, this.current);
\r
832 // reset the current
\r
833 this.current = null;
\r
836 this.stopped = true;
\r
841 * extend eventData for Hammer.gestures
\r
842 * @param {Object} ev
\r
843 * @returns {Object} ev
\r
845 extendEventData: function extendEventData(ev) {
\r
846 var startEv = this.current.startEvent;
\r
848 // if the touches change, set the new touches over the startEvent touches
\r
849 // this because touchevents don't have all the touches on touchstart, or the
\r
850 // user must place his fingers at the EXACT same time on the screen, which is not realistic
\r
851 // but, sometimes it happens that both fingers are touching at the EXACT same time
\r
852 if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
\r
853 // extend 1 level deep to get the touchlist with the touch objects
\r
854 startEv.touches = [];
\r
855 for(var i=0,len=ev.touches.length; i<len; i++) {
\r
856 startEv.touches.push(Hammer.utils.extend({}, ev.touches[i]));
\r
860 var delta_time = ev.timeStamp - startEv.timeStamp,
\r
861 delta_x = ev.center.pageX - startEv.center.pageX,
\r
862 delta_y = ev.center.pageY - startEv.center.pageY,
\r
863 velocity = Hammer.utils.getVelocity(delta_time, delta_x, delta_y);
\r
865 Hammer.utils.extend(ev, {
\r
866 deltaTime : delta_time,
\r
871 velocityX : velocity.x,
\r
872 velocityY : velocity.y,
\r
874 distance : Hammer.utils.getDistance(startEv.center, ev.center),
\r
875 angle : Hammer.utils.getAngle(startEv.center, ev.center),
\r
876 direction : Hammer.utils.getDirection(startEv.center, ev.center),
\r
878 scale : Hammer.utils.getScale(startEv.touches, ev.touches),
\r
879 rotation : Hammer.utils.getRotation(startEv.touches, ev.touches),
\r
881 startEvent : startEv
\r
889 * register new gesture
\r
890 * @param {Object} gesture object, see gestures.js for documentation
\r
891 * @returns {Array} gestures
\r
893 register: function register(gesture) {
\r
894 // add an enable gesture options if there is no given
\r
895 var options = gesture.defaults || {};
\r
896 if(options[gesture.name] === undefined) {
\r
897 options[gesture.name] = true;
\r
900 // extend Hammer default options with the Hammer.gesture options
\r
901 Hammer.utils.extend(Hammer.defaults, options, true);
\r
904 gesture.index = gesture.index || 1000;
\r
906 // add Hammer.gesture to the list
\r
907 this.gestures.push(gesture);
\r
909 // sort the list by index
\r
910 this.gestures.sort(function(a, b) {
\r
911 if (a.index < b.index) {
\r
914 if (a.index > b.index) {
\r
920 return this.gestures;
\r
925 Hammer.gestures = Hammer.gestures || {};
\r
929 * ==============================
\r
932 * --------------------
\r
933 * The object structure of a gesture:
\r
935 * { name: 'mygesture',
\r
938 * mygesture_option: true
\r
940 * handler: function(type, ev, inst) {
\r
941 * // trigger gesture event
\r
942 * inst.trigger(this.name, ev);
\r
946 * @param {String} name
\r
947 * this should be the name of the gesture, lowercase
\r
948 * it is also being used to disable/enable the gesture per instance config.
\r
950 * @param {Number} [index=1000]
\r
951 * the index of the gesture, where it is going to be in the stack of gestures detection
\r
952 * like when you build an gesture that depends on the drag gesture, it is a good
\r
953 * idea to place it after the index of the drag gesture.
\r
955 * @param {Object} [defaults={}]
\r
956 * the default settings of the gesture. these are added to the instance settings,
\r
957 * and can be overruled per instance. you can also add the name of the gesture,
\r
958 * but this is also added by default (and set to true).
\r
960 * @param {Function} handler
\r
961 * this handles the gesture detection of your custom gesture and receives the
\r
962 * following arguments:
\r
964 * @param {Object} eventData
\r
965 * event data containing the following properties:
\r
966 * timeStamp {Number} time the event occurred
\r
967 * target {HTMLElement} target element
\r
968 * touches {Array} touches (fingers, pointers, mouse) on the screen
\r
969 * pointerType {String} kind of pointer that was used. matches Hammer.POINTER_MOUSE|TOUCH
\r
970 * center {Object} center position of the touches. contains pageX and pageY
\r
971 * deltaTime {Number} the total time of the touches in the screen
\r
972 * deltaX {Number} the delta on x axis we haved moved
\r
973 * deltaY {Number} the delta on y axis we haved moved
\r
974 * velocityX {Number} the velocity on the x
\r
975 * velocityY {Number} the velocity on y
\r
976 * angle {Number} the angle we are moving
\r
977 * direction {String} the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT
\r
978 * distance {Number} the distance we haved moved
\r
979 * scale {Number} scaling of the touches, needs 2 touches
\r
980 * rotation {Number} rotation of the touches, needs 2 touches *
\r
981 * eventType {String} matches Hammer.EVENT_START|MOVE|END
\r
982 * srcEvent {Object} the source event, like TouchStart or MouseDown *
\r
983 * startEvent {Object} contains the same properties as above,
\r
984 * but from the first touch. this is used to calculate
\r
985 * distances, deltaTime, scaling etc
\r
987 * @param {Hammer.Instance} inst
\r
988 * the instance we are doing the detection for. you can get the options from
\r
989 * the inst.options object and trigger the gesture event by calling inst.trigger
\r
993 * --------------------
\r
994 * inside the handler you can get/set Hammer.detection.current. This is the current
\r
995 * detection session. It has the following properties
\r
996 * @param {String} name
\r
997 * contains the name of the gesture we have detected. it has not a real function,
\r
998 * only to check in other gestures if something is detected.
\r
999 * like in the drag gesture we set it to 'drag' and in the swipe gesture we can
\r
1000 * check if the current gesture is 'drag' by accessing Hammer.detection.current.name
\r
1003 * @param {Hammer.Instance} inst
\r
1004 * the instance we do the detection for
\r
1007 * @param {Object} startEvent
\r
1008 * contains the properties of the first gesture detection in this session.
\r
1009 * Used for calculations about timing, distance, etc.
\r
1012 * @param {Object} lastEvent
\r
1013 * contains all the properties of the last gesture detect in this session.
\r
1015 * after the gesture detection session has been completed (user has released the screen)
\r
1016 * the Hammer.detection.current object is copied into Hammer.detection.previous,
\r
1017 * this is usefull for gestures like doubletap, where you need to know if the
\r
1018 * previous gesture was a tap
\r
1020 * options that have been set by the instance can be received by calling inst.options
\r
1022 * You can trigger a gesture event by calling inst.trigger("mygesture", event).
\r
1023 * The first param is the name of your gesture, the second the event argument
\r
1026 * Register gestures
\r
1027 * --------------------
\r
1028 * When an gesture is added to the Hammer.gestures object, it is auto registered
\r
1029 * at the setup of the first Hammer instance. You can also call Hammer.detection.register
\r
1030 * manually and pass your gesture object as a param
\r
1036 * Touch stays at the same place for x time
\r
1039 Hammer.gestures.Hold = {
\r
1043 hold_timeout : 500,
\r
1044 hold_threshold : 1
\r
1047 handler: function holdGesture(ev, inst) {
\r
1048 switch(ev.eventType) {
\r
1049 case Hammer.EVENT_START:
\r
1050 // clear any running timers
\r
1051 clearTimeout(this.timer);
\r
1053 // set the gesture so we can check in the timeout if it still is
\r
1054 Hammer.detection.current.name = this.name;
\r
1056 // set timer and if after the timeout it still is hold,
\r
1057 // we trigger the hold event
\r
1058 this.timer = setTimeout(function() {
\r
1059 if(Hammer.detection.current.name == 'hold') {
\r
1060 inst.trigger('hold', ev);
\r
1062 }, inst.options.hold_timeout);
\r
1065 // when you move or end we clear the timer
\r
1066 case Hammer.EVENT_MOVE:
\r
1067 if(ev.distance > inst.options.hold_threshold) {
\r
1068 clearTimeout(this.timer);
\r
1072 case Hammer.EVENT_END:
\r
1073 clearTimeout(this.timer);
\r
1082 * Quick touch at a place or double at the same place
\r
1083 * @events tap, doubletap
\r
1085 Hammer.gestures.Tap = {
\r
1089 tap_max_touchtime : 250,
\r
1090 tap_max_distance : 10,
\r
1091 tap_always : true,
\r
1092 doubletap_distance : 20,
\r
1093 doubletap_interval : 300
\r
1095 handler: function tapGesture(ev, inst) {
\r
1096 if(ev.eventType == Hammer.EVENT_END) {
\r
1097 // previous gesture, for the double tap since these are two different gesture detections
\r
1098 var prev = Hammer.detection.previous,
\r
1099 did_doubletap = false;
\r
1101 // when the touchtime is higher then the max touch time
\r
1102 // or when the moving distance is too much
\r
1103 if(ev.deltaTime > inst.options.tap_max_touchtime ||
\r
1104 ev.distance > inst.options.tap_max_distance) {
\r
1108 // check if double tap
\r
1109 if(prev && prev.name == 'tap' &&
\r
1110 (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
\r
1111 ev.distance < inst.options.doubletap_distance) {
\r
1112 inst.trigger('doubletap', ev);
\r
1113 did_doubletap = true;
\r
1116 // do a single tap
\r
1117 if(!did_doubletap || inst.options.tap_always) {
\r
1118 Hammer.detection.current.name = 'tap';
\r
1119 inst.trigger(Hammer.detection.current.name, ev);
\r
1128 * triggers swipe events when the end velocity is above the threshold
\r
1129 * @events swipe, swipeleft, swiperight, swipeup, swipedown
\r
1131 Hammer.gestures.Swipe = {
\r
1135 // set 0 for unlimited, but this can conflict with transform
\r
1136 swipe_max_touches : 1,
\r
1137 swipe_velocity : 0.7
\r
1139 handler: function swipeGesture(ev, inst) {
\r
1140 if(ev.eventType == Hammer.EVENT_END) {
\r
1142 if(inst.options.swipe_max_touches > 0 &&
\r
1143 ev.touches.length > inst.options.swipe_max_touches) {
\r
1147 // when the distance we moved is too small we skip this gesture
\r
1148 // or we can be already in dragging
\r
1149 if(ev.velocityX > inst.options.swipe_velocity ||
\r
1150 ev.velocityY > inst.options.swipe_velocity) {
\r
1151 // trigger swipe events
\r
1152 inst.trigger(this.name, ev);
\r
1153 inst.trigger(this.name + ev.direction, ev);
\r
1162 * Move with x fingers (default 1) around on the page. Blocking the scrolling when
\r
1163 * moving left and right is a good practice. When all the drag events are blocking
\r
1164 * you disable scrolling on that area.
\r
1165 * @events drag, drapleft, dragright, dragup, dragdown
\r
1167 Hammer.gestures.Drag = {
\r
1171 drag_min_distance : 10,
\r
1172 // set 0 for unlimited, but this can conflict with transform
\r
1173 drag_max_touches : 1,
\r
1174 // prevent default browser behavior when dragging occurs
\r
1175 // be careful with it, it makes the element a blocking element
\r
1176 // when you are using the drag gesture, it is a good practice to set this true
\r
1177 drag_block_horizontal : false,
\r
1178 drag_block_vertical : false,
\r
1179 // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
\r
1180 // It disallows vertical directions if the initial direction was horizontal, and vice versa.
\r
1181 drag_lock_to_axis : false,
\r
1182 // drag lock only kicks in when distance > drag_lock_min_distance
\r
1183 // This way, locking occurs only when the distance has become large enough to reliably determine the direction
\r
1184 drag_lock_min_distance : 25
\r
1187 handler: function dragGesture(ev, inst) {
\r
1188 // current gesture isnt drag, but dragged is true
\r
1189 // this means an other gesture is busy. now call dragend
\r
1190 if(Hammer.detection.current.name != this.name && this.triggered) {
\r
1191 inst.trigger(this.name +'end', ev);
\r
1192 this.triggered = false;
\r
1197 if(inst.options.drag_max_touches > 0 &&
\r
1198 ev.touches.length > inst.options.drag_max_touches) {
\r
1202 switch(ev.eventType) {
\r
1203 case Hammer.EVENT_START:
\r
1204 this.triggered = false;
\r
1207 case Hammer.EVENT_MOVE:
\r
1208 // when the distance we moved is too small we skip this gesture
\r
1209 // or we can be already in dragging
\r
1210 if(ev.distance < inst.options.drag_min_distance &&
\r
1211 Hammer.detection.current.name != this.name) {
\r
1215 // we are dragging!
\r
1216 Hammer.detection.current.name = this.name;
\r
1218 // lock drag to axis?
\r
1219 if(Hammer.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance<=ev.distance)) {
\r
1220 ev.drag_locked_to_axis = true;
\r
1222 var last_direction = Hammer.detection.current.lastEvent.direction;
\r
1223 if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
\r
1224 // keep direction on the axis that the drag gesture started on
\r
1225 if(Hammer.utils.isVertical(last_direction)) {
\r
1226 ev.direction = (ev.deltaY < 0) ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN;
\r
1229 ev.direction = (ev.deltaX < 0) ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT;
\r
1233 // first time, trigger dragstart event
\r
1234 if(!this.triggered) {
\r
1235 inst.trigger(this.name +'start', ev);
\r
1236 this.triggered = true;
\r
1239 // trigger normal event
\r
1240 inst.trigger(this.name, ev);
\r
1242 // direction event, like dragdown
\r
1243 inst.trigger(this.name + ev.direction, ev);
\r
1245 // block the browser events
\r
1246 if( (inst.options.drag_block_vertical && Hammer.utils.isVertical(ev.direction)) ||
\r
1247 (inst.options.drag_block_horizontal && !Hammer.utils.isVertical(ev.direction))) {
\r
1248 ev.preventDefault();
\r
1252 case Hammer.EVENT_END:
\r
1253 // trigger dragend
\r
1254 if(this.triggered) {
\r
1255 inst.trigger(this.name +'end', ev);
\r
1258 this.triggered = false;
\r
1267 * User want to scale or rotate with 2 fingers
\r
1268 * @events transform, pinch, pinchin, pinchout, rotate
\r
1270 Hammer.gestures.Transform = {
\r
1271 name: 'transform',
\r
1274 // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
\r
1275 transform_min_scale : 0.01,
\r
1276 // rotation in degrees
\r
1277 transform_min_rotation : 1,
\r
1278 // prevent default browser behavior when two touches are on the screen
\r
1279 // but it makes the element a blocking element
\r
1280 // when you are using the transform gesture, it is a good practice to set this true
\r
1281 transform_always_block : false
\r
1284 handler: function transformGesture(ev, inst) {
\r
1285 // current gesture isnt drag, but dragged is true
\r
1286 // this means an other gesture is busy. now call dragend
\r
1287 if(Hammer.detection.current.name != this.name && this.triggered) {
\r
1288 inst.trigger(this.name +'end', ev);
\r
1289 this.triggered = false;
\r
1293 // atleast multitouch
\r
1294 if(ev.touches.length < 2) {
\r
1298 // prevent default when two fingers are on the screen
\r
1299 if(inst.options.transform_always_block) {
\r
1300 ev.preventDefault();
\r
1303 switch(ev.eventType) {
\r
1304 case Hammer.EVENT_START:
\r
1305 this.triggered = false;
\r
1308 case Hammer.EVENT_MOVE:
\r
1309 var scale_threshold = Math.abs(1-ev.scale);
\r
1310 var rotation_threshold = Math.abs(ev.rotation);
\r
1312 // when the distance we moved is too small we skip this gesture
\r
1313 // or we can be already in dragging
\r
1314 if(scale_threshold < inst.options.transform_min_scale &&
\r
1315 rotation_threshold < inst.options.transform_min_rotation) {
\r
1319 // we are transforming!
\r
1320 Hammer.detection.current.name = this.name;
\r
1322 // first time, trigger dragstart event
\r
1323 if(!this.triggered) {
\r
1324 inst.trigger(this.name +'start', ev);
\r
1325 this.triggered = true;
\r
1328 inst.trigger(this.name, ev); // basic transform event
\r
1330 // trigger rotate event
\r
1331 if(rotation_threshold > inst.options.transform_min_rotation) {
\r
1332 inst.trigger('rotate', ev);
\r
1335 // trigger pinch event
\r
1336 if(scale_threshold > inst.options.transform_min_scale) {
\r
1337 inst.trigger('pinch', ev);
\r
1338 inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev);
\r
1342 case Hammer.EVENT_END:
\r
1343 // trigger dragend
\r
1344 if(this.triggered) {
\r
1345 inst.trigger(this.name +'end', ev);
\r
1348 this.triggered = false;
\r
1357 * Called as first, tells the user has touched the screen
\r
1360 Hammer.gestures.Touch = {
\r
1364 // call preventDefault at touchstart, and makes the element blocking by
\r
1365 // disabling the scrolling of the page, but it improves gestures like
\r
1366 // transforming and dragging.
\r
1367 // be careful with using this, it can be very annoying for users to be stuck
\r
1369 prevent_default: false,
\r
1371 // disable mouse events, so only touch (or pen!) input triggers events
\r
1372 prevent_mouseevents: false
\r
1374 handler: function touchGesture(ev, inst) {
\r
1375 if(inst.options.prevent_mouseevents && ev.pointerType == Hammer.POINTER_MOUSE) {
\r
1380 if(inst.options.prevent_default) {
\r
1381 ev.preventDefault();
\r
1384 if(ev.eventType == Hammer.EVENT_START) {
\r
1385 inst.trigger(this.name, ev);
\r
1393 * Called as last, tells the user has released the screen
\r
1396 Hammer.gestures.Release = {
\r
1399 handler: function releaseGesture(ev, inst) {
\r
1400 if(ev.eventType == Hammer.EVENT_END) {
\r
1401 inst.trigger(this.name, ev);
\r
1407 if(typeof module === 'object' && typeof module.exports === 'object'){
\r
1408 module.exports = Hammer;
\r
1410 // just window export
\r
1412 window.Hammer = Hammer;
\r
1414 // requireJS module definition
\r
1415 if(typeof window.define === 'function' && window.define.amd) {
\r
1416 window.define('hammer', [], function() {
\r
1424 angular.module('angular-gestures', []);
\r
1427 * Inspired by AngularJS' implementation of "click dblclick mousedown..."
\r
1429 * This ties in the Hammer 1.0.0 events to attributes like:
\r
1431 * hm-tap="add_something()" hm-swipe="remove_something()"
\r
1433 * and also has support for Hammer options with:
\r
1435 * hm-tap-opts="{hold: false}"
\r
1437 * or any other of the "hm-event" listed underneath.
\r
1440 hmDoubleTap : 'doubletap',
\r
1441 hmDragstart : 'dragstart',
\r
1443 hmDragUp : 'dragup',
\r
1444 hmDragDown : 'dragdown',
\r
1445 hmDragLeft : 'dragleft',
\r
1446 hmDragRight : 'dragright',
\r
1447 hmDragend : 'dragend',
\r
1449 hmPinch : 'pinch',
\r
1450 hmPinchIn : 'pinchin',
\r
1451 hmPinchOut : 'pinchout',
\r
1452 hmRelease : 'release',
\r
1453 hmRotate : 'rotate',
\r
1454 hmSwipe : 'swipe',
\r
1455 hmSwipeUp : 'swipeup',
\r
1456 hmSwipeDown : 'swipedown',
\r
1457 hmSwipeLeft : 'swipeleft',
\r
1458 hmSwipeRight : 'swiperight',
\r
1460 hmTouch : 'touch',
\r
1461 hmTransformstart : 'transformstart',
\r
1462 hmTransform : 'transform',
\r
1463 hmTransformend : 'transformend'
\r
1466 var VERBOSE = false;
\r
1468 angular.forEach(HGESTURES, function(eventName, directiveName) {
\r
1469 angular.module('angular-gestures').directive(
\r
1471 ['$parse', '$log', '$timeout', function($parse, $log, $timeout) {
\r
1472 return function(scope, element, attr) {
\r
1473 var hammertime, handler;
\r
1474 attr.$observe(directiveName, function(value) {
\r
1475 var fn = $parse(value);
\r
1476 var opts = $parse(attr[directiveName + 'Opts'])
\r
1478 hammertime = new Hammer(element[0], opts);
\r
1479 handler = function(event) {
\r
1481 $log.debug('angular-gestures: %s',
\r
1484 $timeout(function() {
\r
1485 fn(scope, { $event : event });
\r
1488 hammertime.on(eventName, handler);
\r
1490 scope.$on('$destroy', function() {
\r
1491 hammertime.off(eventName, handler);
\r