/* * hammer.js * version 0.6.1 * author: eight media * https://github.com/eightmedia/hammer.js * licensed under the mit license. */ 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, swipe : true, swipe_time : 200, // ms swipe_min_distance : 20, // pixels 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_max_distance : 10, 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; var _has_touch = ('ontouchstart' in window); /** * option setter/getter * @param string key * @param mixed value * @return mixed value */ this.option = function(key, val) { if(val != undefined) { options[key] = val; } return options[key]; }; /** * 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; }; /** * destory events * @return void */ this.destroy = function() { if(_has_touch) { removeevent(element, "touchstart touchmove touchend touchcancel", handleevents); } // for non-touch else { removeevent(element, "mouseup mousedown mousemove", handleevents); removeevent(element, "mouseout", handlemouseout); } }; /** * 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(!_has_touch) { 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 touch_time) && (_distance > options.swipe_min_distance)) { // calculate the angle _angle = getangle(_pos.start[0], _pos.move[0]); _direction = self.getdirectionfromangle(_angle); _gesture = 'swipe'; 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 }; // normal slide event triggerevent("swipe", event_obj); } }, // 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) { if(countfingers(event) != 2) { return false; } var rotation = calculaterotation(_pos.start, _pos.move); var scale = calculatescale(_pos.start, _pos.move); 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 { var x_distance = (_pos.move) ? math.abs(_pos.move[0].x - _pos.start[0].x) : 0; var y_distance = (_pos.move) ? math.abs(_pos.move[0].y - _pos.start[0].y) : 0; _distance = math.max(x_distance, y_distance); if(_distance < options.tap_max_distance) { _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; // swipe gesture gestures.swipe(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 : calculatescale(_pos.start, _pos.move), rotation : calculaterotation(_pos.start, _pos.move) }); } else { gestures.tap(_event_start); } _prev_gesture = _gesture; // trigger release event triggerevent("release", { originalevent : event, gesture : _gesture }); // reset vars reset(); break; } } function handlemouseout(event) { if(!isinsidehammer(element, event.relatedtarget)) { handleevents(event); } } // bind events for touch devices // except for windows phone 7.5, it doesnt support touch events..! if(_has_touch) { addevent(element, "touchstart touchmove touchend touchcancel", handleevents); } // for non-touch else { addevent(element, "mouseup mousedown mousemove", handleevents); addevent(element, "mouseout", handlemouseout); } /** * 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; } /** * check if object is a function * @param object obj * @return bool is function */ function isfunction( obj ){ return object.prototype.tostring.call( obj ) == "[object function]"; } /** * attach event * @param node element * @param string types * @param object callback */ function addevent(element, types, callback) { types = types.split(" "); for(var t= 0,len=types.length; t