586 satır
No EOL
17 KiB
JavaScript
Çalıştırılabilir Dosya
586 satır
No EOL
17 KiB
JavaScript
Çalıştırılabilir Dosya
/*
|
|
* 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]";
|
|
}
|
|
} |