Source: ScrollMagic/_util.js

/*
 * TODO: DOCS (private for dev)
 */

var _util = ScrollMagic._util = (function (window) {
	var U = {}, i;
	
	/**
	 * ------------------------------
	 * internal helpers
	 * ------------------------------
	 */

	 // parse float and fall back to 0.
	var floatval = function (number) {
	 	return parseFloat(number) || 0;
	};
	 // get current style IE safe (otherwise IE would return calculated values for 'auto')
	var _getComputedStyle = function (elem) {
		return elem.currentStyle ? elem.currentStyle : window.getComputedStyle(elem);
	};

	// get element dimension (width or height)
	var _dimension = function (which, elem, outer, includeMargin) {
		elem = (elem === document) ? window : elem;
		if (elem === window) {
			includeMargin = false;
		} else if (!_type.DomElement(elem)) {
			return 0;
		}
		which = which.charAt(0).toUpperCase() + which.substr(1).toLowerCase();
		var dimension = (outer ? elem['offset' + which] || elem['outer' + which] : elem['client' + which] || elem['inner' + which]) || 0;
		if (outer && includeMargin) {
			var style = _getComputedStyle(elem);
			dimension += which === 'Height' ?  floatval(style.marginTop) + floatval(style.marginBottom) : floatval(style.marginLeft) + floatval(style.marginRight);
		}
		return dimension;
	};
	// converts 'margin-top' into 'marginTop'
	var _camelCase = function (str) {
		return str.replace(/^[^a-z]+([a-z])/g, '$1').replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
	};

	/**
	 * ------------------------------
	 * external helpers
	 * ------------------------------
	 */

	// extend obj – same as jQuery.extend({}, objA, objB)
	U.extend = function (obj) {
		obj = obj || {};
		for (i = 1; i < arguments.length; i++) {
			if (!arguments[i]) {
				continue;
			}
			for (var key in arguments[i]) {
				if (arguments[i].hasOwnProperty(key)) {
					obj[key] = arguments[i][key];
				}
			}
		}
		return obj;
	};

	// check if a css display type results in margin-collapse or not
	U.isMarginCollapseType = function (str) {
		return ["block", "flex", "list-item", "table", "-webkit-box"].indexOf(str) > -1;
	};

	// implementation of requestAnimationFrame
	// based on https://gist.github.com/paulirish/1579671
	var
		lastTime = 0,
		vendors = ['ms', 'moz', 'webkit', 'o'];
	var _requestAnimationFrame = window.requestAnimationFrame;
	var _cancelAnimationFrame = window.cancelAnimationFrame;
	// try vendor prefixes if the above doesn't work
	for (i = 0; !_requestAnimationFrame && i < vendors.length; ++i) {
		_requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame'];
		_cancelAnimationFrame = window[vendors[i] + 'CancelAnimationFrame'] || window[vendors[i] + 'CancelRequestAnimationFrame'];
	}

	// fallbacks
	if (!_requestAnimationFrame) {
		_requestAnimationFrame = function (callback) {
			var
				currTime = new Date().getTime(),
				timeToCall = Math.max(0, 16 - (currTime - lastTime)),
				id = window.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall);
			lastTime = currTime + timeToCall;
			return id;
		};
	}
	if (!_cancelAnimationFrame) {
		_cancelAnimationFrame = function (id) {
			window.clearTimeout(id);
		};
	}
	U.rAF = _requestAnimationFrame.bind(window);
	U.cAF = _cancelAnimationFrame.bind(window);

	// (BUILD) - REMOVE IN MINIFY - START
	var
		loglevels = ["error", "warn", "log"],
		console = window.console || {};

	console.log = console.log || function(){}; // no console log, well - do nothing then...
	// make sure methods for all levels exist.
	for(i = 0; i<loglevels.length; i++) {
		var method = loglevels[i];
		if (!console[method]) {
			console[method] = console.log; // prefer .log over nothing
		}
	}
	U.log = function (loglevel) {
		if (loglevel > loglevels.length || loglevel <= 0) loglevel = loglevels.length;
		var now = new Date(),
			time = ("0"+now.getHours()).slice(-2) + ":" + ("0"+now.getMinutes()).slice(-2) + ":" + ("0"+now.getSeconds()).slice(-2) + ":" + ("00"+now.getMilliseconds()).slice(-3),
			method = loglevels[loglevel-1],
			args = Array.prototype.splice.call(arguments, 1),
			func = Function.prototype.bind.call(console[method], console);
		args.unshift(time);
		func.apply(console, args);
	};
	// (BUILD) - REMOVE IN MINIFY - END

	/**
	 * ------------------------------
	 * type testing
	 * ------------------------------
	 */

	var _type = U.type = function (v) {
		return Object.prototype.toString.call(v).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
	};
	_type.String = function (v) {
		return _type(v) === 'string';
	};
	_type.Function = function (v) {
		return _type(v) === 'function';
	};
	_type.Array = function (v) {
		return Array.isArray(v);
	};
	_type.Number = function (v) {
		return !_type.Array(v) && (v - parseFloat(v) + 1) >= 0;
	};
	_type.DomElement = function (o){
		return (
			typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
			o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
		);
	};

	/**
	 * ------------------------------
	 * DOM Element info
	 * ------------------------------
	 */
	// always returns a list of matching DOM elements, from a selector, a DOM element or an list of elements or even an array of selectors
	var _get = U.get = {};
	_get.elements = function (selector) {
		var arr = [];
		if (_type.String(selector)) {
			try {
				selector = document.querySelectorAll(selector);
			} catch (e) { // invalid selector
				return arr;
			}
		}
		if (_type(selector) === 'nodelist' || _type.Array(selector)) {
			for (var i = 0, ref = arr.length = selector.length; i < ref; i++) { // list of elements
				var elem = selector[i];
				arr[i] = _type.DomElement(elem) ? elem : _get.elements(elem); // if not an element, try to resolve recursively
			}
		} else if (_type.DomElement(selector) || selector === document || selector === window){
			arr = [selector]; // only the element
		}
		return arr;
	};
	// get scroll top value
	_get.scrollTop = function (elem) {
		return (elem && typeof elem.scrollTop === 'number') ? elem.scrollTop : window.pageYOffset || 0;
	};
	// get scroll left value
	_get.scrollLeft = function (elem) {
		return (elem && typeof elem.scrollLeft === 'number') ? elem.scrollLeft : window.pageXOffset || 0;
	};
	// get element height
	_get.width = function (elem, outer, includeMargin) {
		return _dimension('width', elem, outer, includeMargin);
	};
	// get element width
	_get.height = function (elem, outer, includeMargin) {
		return _dimension('height', elem, outer, includeMargin);
	};

	// get element position (optionally relative to viewport)
	_get.offset = function (elem, relativeToViewport) {
		var offset = {top: 0, left: 0};
		if (elem && elem.getBoundingClientRect) { // check if available
			var rect = elem.getBoundingClientRect();
			offset.top = rect.top;
			offset.left = rect.left;
			if (!relativeToViewport) { // clientRect is by default relative to viewport...
				offset.top += _get.scrollTop();
				offset.left += _get.scrollLeft();
			}
		}
		return offset;
	};

	/**
	 * ------------------------------
	 * DOM Element manipulation
	 * ------------------------------
	 */

	U.addClass = function(elem, classname) {
		if (classname) {
			if (elem.classList)
				elem.classList.add(classname);
			else
				elem.className += ' ' + classname;
		}
	};
	U.removeClass = function(elem, classname) {
		if (classname) {
			if (elem.classList)
				elem.classList.remove(classname);
			else
				elem.className = elem.className.replace(new RegExp('(^|\\b)' + classname.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
		}
	};
	// if options is string -> returns css value
	// if options is array -> returns object with css value pairs
	// if options is object -> set new css values
	U.css = function (elem, options) {
		if (_type.String(options)) {
			return _getComputedStyle(elem)[_camelCase(options)];
		} else if (_type.Array(options)) {
			var
				obj = {},
				style = _getComputedStyle(elem);
			options.forEach(function(option, key) {
				obj[option] = style[_camelCase(option)];
			});
			return obj;
		} else {
			for (var option in options) {
				var val = options[option];
				if (val == parseFloat(val)) { // assume pixel for seemingly numerical values
					val += 'px';
				}
				elem.style[_camelCase(option)] = val;
			}
		}
	};

	return U;
}(window || {}));