123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- /*
- * Hammer.JS
- * version 0.4
- * author: Eight Media
- * https://github.com/EightMedia/hammer.js
- */
- function Hammer(element, options, undefined)
- {
- var self = this;
- var defaults = {
- // prevent the default event or not... might be buggy when false
- prevent_default : false,
- css_hacks : true,
- drag : true,
- drag_vertical : true,
- drag_horizontal : true,
- // minimum distance before the drag event starts
- drag_min_distance : 20, // pixels
- // pinch zoom and rotation
- transform : true,
- scale_treshold : 0.1,
- rotation_treshold : 15, // degrees
- tap : true,
- tap_double : true,
- tap_max_interval : 300,
- tap_double_distance: 20,
- hold : true,
- hold_timeout : 500
- };
- options = mergeObject(defaults, options);
- // some css hacks
- (function() {
- if(!options.css_hacks) {
- return false;
- }
- var vendors = ['webkit','moz','ms','o',''];
- var css_props = {
- "userSelect": "none",
- "touchCallout": "none",
- "userDrag": "none",
- "tapHighlightColor": "rgba(0,0,0,0)"
- };
- var prop = '';
- for(var i = 0; i < vendors.length; i++) {
- for(var p in css_props) {
- prop = p;
- if(vendors[i]) {
- prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1);
- }
- element.style[ prop ] = css_props[p];
- }
- }
- })();
- // holds the distance that has been moved
- var _distance = 0;
- // holds the exact angle that has been moved
- var _angle = 0;
- // holds the diraction that has been moved
- var _direction = 0;
- // holds position movement for sliding
- var _pos = { };
- // how many fingers are on the screen
- var _fingers = 0;
- var _first = false;
- var _gesture = null;
- var _prev_gesture = null;
- var _touch_start_time = null;
- var _prev_tap_pos = {x: 0, y: 0};
- var _prev_tap_end_time = null;
- var _hold_timer = null;
- var _offset = {};
- // keep track of the mouse status
- var _mousedown = false;
- var _event_start;
- var _event_move;
- var _event_end;
- /**
- * angle to direction define
- * @param float angle
- * @return string direction
- */
- this.getDirectionFromAngle = function( angle )
- {
- var directions = {
- down: angle >= 45 && angle < 135, //90
- left: angle >= 135 || angle <= -135, //180
- up: angle < -45 && angle > -135, //270
- right: angle >= -45 && angle <= 45 //0
- };
- var direction, key;
- for(key in directions){
- if(directions[key]){
- direction = key;
- break;
- }
- }
- return direction;
- };
- /**
- * count the number of fingers in the event
- * when no fingers are detected, one finger is returned (mouse pointer)
- * @param event
- * @return int fingers
- */
- function countFingers( event )
- {
- // there is a bug on android (until v4?) that touches is always 1,
- // so no multitouch is supported, e.g. no, zoom and rotation...
- return event.touches ? event.touches.length : 1;
- }
- /**
- * get the x and y positions from the event object
- * @param event
- * @return array [{ x: int, y: int }]
- */
- function getXYfromEvent( event )
- {
- event = event || window.event;
- // no touches, use the event pageX and pageY
- if(!event.touches) {
- var doc = document,
- body = doc.body;
- return [{
- x: event.pageX || event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && doc.clientLeft || 0 ),
- y: event.pageY || event.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && doc.clientTop || 0 )
- }];
- }
- // multitouch, return array with positions
- else {
- var pos = [], src;
- for(var t=0, len=event.touches.length; t<len; t++) {
- src = event.touches[t];
- pos.push({ x: src.pageX, y: src.pageY });
- }
- return pos;
- }
- }
- /**
- * calculate the angle between two points
- * @param object pos1 { x: int, y: int }
- * @param object pos2 { x: int, y: int }
- */
- function getAngle( pos1, pos2 )
- {
- return Math.atan2(pos2.y - pos1.y, pos2.x - pos1.x) * 180 / Math.PI;
- }
- /**
- * trigger an event/callback by name with params
- * @param string name
- * @param array params
- */
- function triggerEvent( eventName, params )
- {
- // return touches object
- params.touches = getXYfromEvent(params.originalEvent);
- params.type = eventName;
- // trigger callback
- if(isFunction(self["on"+ eventName])) {
- self["on"+ eventName].call(self, params);
- }
- }
- /**
- * cancel event
- * @param object event
- * @return void
- */
- function cancelEvent(event){
- event = event || window.event;
- if(event.preventDefault){
- event.preventDefault();
- }else{
- event.returnValue = false;
- event.cancelBubble = true;
- }
- }
- /**
- * reset the internal vars to the start values
- */
- function reset()
- {
- _pos = {};
- _first = false;
- _fingers = 0;
- _distance = 0;
- _angle = 0;
- _gesture = null;
- }
- var gestures = {
- // hold gesture
- // fired on touchstart
- hold : function(event)
- {
- // only when one finger is on the screen
- if(options.hold) {
- _gesture = 'hold';
- clearTimeout(_hold_timer);
- _hold_timer = setTimeout(function() {
- if(_gesture == 'hold') {
- triggerEvent("hold", {
- originalEvent : event,
- position : _pos.start
- });
- }
- }, options.hold_timeout);
- }
- },
- // drag gesture
- // fired on mousemove
- drag : function(event)
- {
- // get the distance we moved
- var _distance_x = _pos.move[0].x - _pos.start[0].x;
- var _distance_y = _pos.move[0].y - _pos.start[0].y;
- _distance = Math.sqrt(_distance_x * _distance_x + _distance_y * _distance_y);
- // drag
- // minimal movement required
- if(options.drag && (_distance > options.drag_min_distance) || _gesture == 'drag') {
- // calculate the angle
- _angle = getAngle(_pos.start[0], _pos.move[0]);
- _direction = self.getDirectionFromAngle(_angle);
- // check the movement and stop if we go in the wrong direction
- var is_vertical = (_direction == 'up' || _direction == 'down');
- if(((is_vertical && !options.drag_vertical) || (!is_vertical && !options.drag_horizontal))
- && (_distance > options.drag_min_distance)) {
- return;
- }
- _gesture = 'drag';
- var position = { x: _pos.move[0].x - _offset.left,
- y: _pos.move[0].y - _offset.top };
- var event_obj = {
- originalEvent : event,
- position : position,
- direction : _direction,
- distance : _distance,
- distanceX : _distance_x,
- distanceY : _distance_y,
- angle : _angle
- };
- // on the first time trigger the start event
- if(_first) {
- triggerEvent("dragstart", event_obj);
- _first = false;
- }
- // normal slide event
- triggerEvent("drag", event_obj);
- cancelEvent(event);
- }
- },
- // transform gesture
- // fired on touchmove
- transform : function(event)
- {
- if(options.transform) {
- var scale = event.scale || 1;
- var rotation = event.rotation || 0;
- if(countFingers(event) != 2) {
- return false;
- }
- if(_gesture != 'drag' &&
- (_gesture == 'transform' || Math.abs(1-scale) > options.scale_treshold
- || Math.abs(rotation) > options.rotation_treshold)) {
- _gesture = 'transform';
- _pos.center = { x: ((_pos.move[0].x + _pos.move[1].x) / 2) - _offset.left,
- y: ((_pos.move[0].y + _pos.move[1].y) / 2) - _offset.top };
- var event_obj = {
- originalEvent : event,
- position : _pos.center,
- scale : scale,
- rotation : rotation
- };
- // on the first time trigger the start event
- if(_first) {
- triggerEvent("transformstart", event_obj);
- _first = false;
- }
- triggerEvent("transform", event_obj);
- cancelEvent(event);
- return true;
- }
- }
- return false;
- },
- // tap and double tap gesture
- // fired on touchend
- tap : function(event)
- {
- // compare the kind of gesture by time
- var now = new Date().getTime();
- var touch_time = now - _touch_start_time;
- // dont fire when hold is fired
- if(options.hold && !(options.hold && options.hold_timeout > touch_time)) {
- return;
- }
- // when previous event was tap and the tap was max_interval ms ago
- var is_double_tap = (function(){
- if (_prev_tap_pos && options.tap_double && _prev_gesture == 'tap' && (_touch_start_time - _prev_tap_end_time) < options.tap_max_interval) {
- var x_distance = Math.abs(_prev_tap_pos[0].x - _pos.start[0].x);
- var y_distance = Math.abs(_prev_tap_pos[0].y - _pos.start[0].y);
- return (_prev_tap_pos && _pos.start && Math.max(x_distance, y_distance) < options.tap_double_distance);
- }
- return false;
- })();
- if(is_double_tap) {
- _gesture = 'double_tap';
- _prev_tap_end_time = null;
- triggerEvent("doubletap", {
- originalEvent : event,
- position : _pos.start
- });
- cancelEvent(event);
- }
- // single tap is single touch
- else {
- _gesture = 'tap';
- _prev_tap_end_time = now;
- _prev_tap_pos = _pos.start;
- if(options.tap) {
- triggerEvent("tap", {
- originalEvent : event,
- position : _pos.start
- });
- cancelEvent(event);
- }
- }
- }
- };
- function handleEvents(event)
- {
- switch(event.type)
- {
- case 'mousedown':
- case 'touchstart':
- _pos.start = getXYfromEvent(event);
- _touch_start_time = new Date().getTime();
- _fingers = countFingers(event);
- _first = true;
- _event_start = event;
- // borrowed from jquery offset https://github.com/jquery/jquery/blob/master/src/offset.js
- var box = element.getBoundingClientRect();
- var clientTop = element.clientTop || document.body.clientTop || 0;
- var clientLeft = element.clientLeft || document.body.clientLeft || 0;
- var scrollTop = window.pageYOffset || element.scrollTop || document.body.scrollTop;
- var scrollLeft = window.pageXOffset || element.scrollLeft || document.body.scrollLeft;
- _offset = {
- top: box.top + scrollTop - clientTop,
- left: box.left + scrollLeft - clientLeft
- };
- _mousedown = true;
- // hold gesture
- gestures.hold(event);
- if(options.prevent_default) {
- cancelEvent(event);
- }
- break;
- case 'mousemove':
- case 'touchmove':
- if(!_mousedown) {
- return false;
- }
- _event_move = event;
- _pos.move = getXYfromEvent(event);
- if(!gestures.transform(event)) {
- gestures.drag(event);
- }
- break;
- case 'mouseup':
- case 'mouseout':
- case 'touchcancel':
- case 'touchend':
- if(!_mousedown || (_gesture != 'transform' && event.touches && event.touches.length > 0)) {
- return false;
- }
- _mousedown = false;
- _event_end = event;
- // drag gesture
- // dragstart is triggered, so dragend is possible
- if(_gesture == 'drag') {
- triggerEvent("dragend", {
- originalEvent : event,
- direction : _direction,
- distance : _distance,
- angle : _angle
- });
- }
- // transform
- // transformstart is triggered, so transformed is possible
- else if(_gesture == 'transform') {
- triggerEvent("transformend", {
- originalEvent : event,
- position : _pos.center,
- scale : event.scale,
- rotation : event.rotation
- });
- }
- else {
- gestures.tap(_event_start);
- }
- _prev_gesture = _gesture;
- // reset vars
- reset();
- break;
- }
- }
- // bind events for touch devices
- // except for windows phone 7.5, it doesnt support touch events..!
- if('ontouchstart' in window) {
- element.addEventListener("touchstart", handleEvents, false);
- element.addEventListener("touchmove", handleEvents, false);
- element.addEventListener("touchend", handleEvents, false);
- element.addEventListener("touchcancel", handleEvents, false);
- }
- // for non-touch
- else {
- if(element.addEventListener){ // prevent old IE errors
- element.addEventListener("mouseout", function(event) {
- if(!isInsideHammer(element, event.relatedTarget)) {
- handleEvents(event);
- }
- }, false);
- element.addEventListener("mouseup", handleEvents, false);
- element.addEventListener("mousedown", handleEvents, false);
- element.addEventListener("mousemove", handleEvents, false);
- // events for older IE
- }else if(document.attachEvent){
- element.attachEvent("onmouseout", function(event) {
- if(!isInsideHammer(element, event.relatedTarget)) {
- handleEvents(event);
- }
- }, false);
- element.attachEvent("onmouseup", handleEvents);
- element.attachEvent("onmousedown", handleEvents);
- element.attachEvent("onmousemove", handleEvents);
- }
- }
- /**
- * find if element is (inside) given parent element
- * @param object element
- * @param object parent
- * @return bool inside
- */
- function isInsideHammer(parent, child) {
- // get related target for IE
- if(!child && window.event && window.event.toElement){
- child = window.event.toElement;
- }
- if(parent === child){
- return true;
- }
- // loop over parentNodes of child until we find hammer element
- if(child){
- var node = child.parentNode;
- while(node !== null){
- if(node === parent){
- return true;
- };
- node = node.parentNode;
- }
- }
- return false;
- }
- /**
- * merge 2 objects into a new object
- * @param object obj1
- * @param object obj2
- * @return object merged object
- */
- function mergeObject(obj1, obj2) {
- var output = {};
- if(!obj2) {
- return obj1;
- }
- for (var prop in obj1) {
- if (prop in obj2) {
- output[prop] = obj2[prop];
- } else {
- output[prop] = obj1[prop];
- }
- }
- return output;
- }
- function isFunction( obj ){
- return Object.prototype.toString.call( obj ) == "[object Function]";
- }
- }
|