src/utils.js
/**
* @module hammer
*
* @class Utils
* @static
*/
var Utils = Hammer.utils = {
/**
* extend method, could also be used for cloning when `dest` is an empty object.
* changes the dest object
* @method extend
* @param {Object} dest
* @param {Object} src
* @param {Boolean} [merge=false] do a merge
* @return {Object} dest
*/
extend: function extend(dest, src, merge) {
for(var key in src) {
if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
continue;
}
dest[key] = src[key];
}
return dest;
},
/**
* simple addEventListener wrapper
* @method on
* @param {HTMLElement} element
* @param {String} type
* @param {Function} handler
*/
on: function on(element, type, handler) {
element.addEventListener(type, handler, false);
},
/**
* simple removeEventListener wrapper
* @method off
* @param {HTMLElement} element
* @param {String} type
* @param {Function} handler
*/
off: function off(element, type, handler) {
element.removeEventListener(type, handler, false);
},
/**
* forEach over arrays and objects
* @method each
* @param {Object|Array} obj
* @param {Function} iterator
* @param {any} iterator.item
* @param {Number} iterator.index
* @param {Object|Array} iterator.obj the source object
* @param {Object} context value to use as `this` in the iterator
*/
each: function each(obj, iterator, context) {
var i, len;
// native forEach on arrays
if('forEach' in obj) {
obj.forEach(iterator, context);
// arrays
} else if(obj.length !== undefined) {
for(i = 0, len = obj.length; i < len; i++) {
if(iterator.call(context, obj[i], i, obj) === false) {
return;
}
}
// objects
} else {
for(i in obj) {
if(obj.hasOwnProperty(i) &&
iterator.call(context, obj[i], i, obj) === false) {
return;
}
}
}
},
/**
* find if a string contains the string using indexOf
* @method inStr
* @param {String} src
* @param {String} find
* @return {Boolean} found
*/
inStr: function inStr(src, find) {
return src.indexOf(find) > -1;
},
/**
* find if a array contains the object using indexOf or a simple polyfill
* @method inArray
* @param {String} src
* @param {String} find
* @return {Boolean|Number} false when not found, or the index
*/
inArray: function inArray(src, find) {
if(src.indexOf) {
var index = src.indexOf(find);
return (index === -1) ? false : index;
} else {
for(var i = 0, len = src.length; i < len; i++) {
if(src[i] === find) {
return i;
}
}
return false;
}
},
/**
* convert an array-like object (`arguments`, `touchlist`) to an array
* @method toArray
* @param {Object} obj
* @return {Array}
*/
toArray: function toArray(obj) {
return Array.prototype.slice.call(obj, 0);
},
/**
* find if a node is in the given parent
* @method hasParent
* @param {HTMLElement} node
* @param {HTMLElement} parent
* @return {Boolean} found
*/
hasParent: function hasParent(node, parent) {
while(node) {
if(node == parent) {
return true;
}
node = node.parentNode;
}
return false;
},
/**
* get the center of all the touches
* @method getCenter
* @param {Array} touches
* @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
*/
getCenter: function getCenter(touches) {
var pageX = [],
pageY = [],
clientX = [],
clientY = [],
min = Math.min,
max = Math.max;
// no need to loop when only one touch
if(touches.length === 1) {
return {
pageX: touches[0].pageX,
pageY: touches[0].pageY,
clientX: touches[0].clientX,
clientY: touches[0].clientY
};
}
Utils.each(touches, function(touch) {
pageX.push(touch.pageX);
pageY.push(touch.pageY);
clientX.push(touch.clientX);
clientY.push(touch.clientY);
});
return {
pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
};
},
/**
* calculate the velocity between two points. unit is in px per ms.
* @method getVelocity
* @param {Number} deltaTime
* @param {Number} deltaX
* @param {Number} deltaY
* @return {Object} velocity `x` and `y`
*/
getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
return {
x: Math.abs(deltaX / deltaTime) || 0,
y: Math.abs(deltaY / deltaTime) || 0
};
},
/**
* calculate the angle between two coordinates
* @method getAngle
* @param {Touch} touch1
* @param {Touch} touch2
* @return {Number} angle
*/
getAngle: function getAngle(touch1, touch2) {
var x = touch2.clientX - touch1.clientX,
y = touch2.clientY - touch1.clientY;
return Math.atan2(y, x) * 180 / Math.PI;
},
/**
* do a small comparision to get the direction between two touches.
* @method getDirection
* @param {Touch} touch1
* @param {Touch} touch2
* @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
*/
getDirection: function getDirection(touch1, touch2) {
var x = Math.abs(touch1.clientX - touch2.clientX),
y = Math.abs(touch1.clientY - touch2.clientY);
if(x >= y) {
return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
}
return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
},
/**
* calculate the distance between two touches
* @method getDistance
* @param {Touch}touch1
* @param {Touch} touch2
* @return {Number} distance
*/
getDistance: function getDistance(touch1, touch2) {
var x = touch2.clientX - touch1.clientX,
y = touch2.clientY - touch1.clientY;
return Math.sqrt((x * x) + (y * y));
},
/**
* calculate the scale factor between two touchLists
* no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
* @method getScale
* @param {Array} start array of touches
* @param {Array} end array of touches
* @return {Number} scale
*/
getScale: function getScale(start, end) {
// need two fingers...
if(start.length >= 2 && end.length >= 2) {
return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
}
return 1;
},
/**
* calculate the rotation degrees between two touchLists
* @method getRotation
* @param {Array} start array of touches
* @param {Array} end array of touches
* @return {Number} rotation
*/
getRotation: function getRotation(start, end) {
// need two fingers
if(start.length >= 2 && end.length >= 2) {
return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
}
return 0;
},
/**
* find out if the direction is vertical *
* @method isVertical
* @param {String} direction matches `DIRECTION_UP|DOWN`
* @return {Boolean} is_vertical
*/
isVertical: function isVertical(direction) {
return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
},
/**
* set css properties with their prefixes
* @param {HTMLElement} element
* @param {String} prop
* @param {String} value
* @param {Boolean} [toggle=true]
* @return {Boolean}
*/
setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
prop = Utils.toCamelCase(prop);
for(var i = 0; i < prefixes.length; i++) {
var p = prop;
// prefixes
if(prefixes[i]) {
p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
}
// test the style
if(p in element.style) {
element.style[p] = (toggle == null || toggle) && value || '';
break;
}
}
},
/**
* toggle browser default behavior by setting css properties.
* `userSelect='none'` also sets `element.onselectstart` to false
* `userDrag='none'` also sets `element.ondragstart` to false
*
* @method toggleBehavior
* @param {HtmlElement} element
* @param {Object} props
* @param {Boolean} [toggle=true]
*/
toggleBehavior: function toggleBehavior(element, props, toggle) {
if(!props || !element || !element.style) {
return;
}
// set the css properties
Utils.each(props, function(value, prop) {
Utils.setPrefixedCss(element, prop, value, toggle);
});
var falseFn = toggle && function() {
return false;
};
// also the disable onselectstart
if(props.userSelect == 'none') {
element.onselectstart = falseFn;
}
// and disable ondragstart
if(props.userDrag == 'none') {
element.ondragstart = falseFn;
}
},
/**
* convert a string with underscores to camelCase
* so prevent_default becomes preventDefault
* @param {String} str
* @return {String} camelCaseStr
*/
toCamelCase: function toCamelCase(str) {
return str.replace(/[_-]([a-z])/g, function(s) {
return s[1].toUpperCase();
});
}
};