hui = window.hui || {};
/////////////////////////// Animation ///////////////////////////
/**
* Animate something
* <pre><strong>options:</strong> {
* node : «Element»,
* css : { fontSize : '11px', color : '#f00', opacity : 0.5 },
* duration : 1000, // 1sec
* ease : function(num) {},
* $complete : function() {}
*}
* TODO Document options.property, options.value
* @memberof hui
*
* @param {Element | Object} options Options or an element
* @param {String} style The css property
* @param {String} value The css value
* @param {Number} duration The duration in milisecons
* @param {Object} delegate The options if first param is an element
*/
hui.animate = function(options, property, value, duration, delegate) {
if (typeof(options) == 'string' || hui.dom.isElement(options)) {
hui.animation.get(options).animate(null, value, property, duration, delegate);
} else {
var item = hui.animation.get(options.node);
if (options.property) {
item.animate(null, options.value, options.property, options.duration, options);
} else if (!options.css) {
item.animate(null, '', '', options.duration, options);
} else {
var o = options;
for (var prop in options.css) {
item.animate(null, options.css[prop], prop, options.duration, o);
o = hui.override({}, options);
o.$complete = undefined;
}
}
}
};
/** @namespace */
hui.animation = {
objects: {},
running: false,
latestId: 0,
/**
* Get an animation item for a node
* @return hui.animation.Item
*/
get: function(element) {
element = hui.get(element);
if (!element.huiAnimationId) {
element.huiAnimationId = this.latestId++;
}
if (!this.objects[element.huiAnimationId]) {
this.objects[element.huiAnimationId] = new hui.animation.Item(element);
}
return this.objects[element.huiAnimationId];
},
/**
* Start animating any pending tasks
*/
start: function() {
if (!this.running) {
hui.animation._render();
}
}
};
hui.animation._lengthUpater = function(element,v,work) {
element.style[work.property] = (work.from + (work.to - work.from) * v) + (work.unit ? work.unit : '');
};
hui.animation._transformUpater = function(element, v, work) {
var t = work.transform;
var str = '';
if (t.rotate) {
str += ' rotate(' + (t.rotate.from + (t.rotate.to - t.rotate.from) * v) + t.rotate.unit + ')';
}
if (t.scale) {
str += ' scale(' + (t.scale.from + (t.scale.to - t.scale.from) * v) + ')';
}
element.style[hui.animation.TRANSFORM] = str;
};
hui.animation._colorUpater = function(element, v, work) {
var red = Math.round(work.from.red + (work.to.red - work.from.red) * v);
var green = Math.round(work.from.green + (work.to.green - work.from.green) * v);
var blue = Math.round(work.from.blue + (work.to.blue - work.from.blue) * v);
if (work.to.alpha < 255 || work.from.alpha < 255) {
var alpha = Math.max(0,Math.min(1,work.from.alpha + (work.to.alpha - work.from.alpha) * v));
element.style[work.property] = 'rgba(' + red + ',' + green + ',' + blue + ',' + alpha + ')';
} else {
element.style[work.property] = 'rgb(' + red + ',' + green + ',' + blue + ')';
}
};
hui.animation._propertyUpater = function(element, v, work) {
element[work.property] = Math.round(work.from+(work.to-work.from)*v);
};
hui.animation._ieOpacityUpdater = function(element, v, work) {
var opacity = (work.from + (work.to - work.from) * v);
if (opacity == 1) {
element.style.removeAttribute('filter');
} else {
element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
}
};
hui.animation._render = function() {
hui.animation.running = true;
var next = false,
stamp = Date.now();
for (var id in hui.animation.objects) {
var obj = hui.animation.objects[id];
if (obj.work) {
var element = obj.element;
for (var i=0; i < obj.work.length; i++) {
var work = obj.work[i];
if (work.finished) {
continue;
}
var place = (stamp-work.start)/(work.end-work.start);
if (place < 0) {
next = true;
continue;
}
else if (isNaN(place) || place>1) {
place = 1;
}
else if (place<1) {
next=true;
}
var v = place,
value = null;
if (work.delegate && work.delegate.ease) {
v = work.delegate.ease(v);
}
if (work.delegate && work.delegate.$render) {
work.delegate.$render(element,v);
} else if (work.delegate && work.delegate.callback) {
work.delegate.callback(element,v);
} else if (work.updater) {
work.updater(element,v,work);
}
if (place==1) {
work.finished = true;
if (work.delegate && work.delegate.$complete) {
window.setTimeout(work.delegate.$complete);
} else if (work.delegate && work.delegate.onComplete) {
window.setTimeout(work.delegate.onComplete);
} else if (work.delegate && work.delegate.hideOnComplete) {
element.style.display='none';
}
}
}
}
}
if (next) {
hui.onDraw(hui.animation._render);
} else {
hui.animation.running = false;
}
};
hui.animation._parseStyle = function(value) {
var parsed = {type:null,value:null,unit:null};
var match;
if (!hui.isDefined(value)) {
return parsed;
}
if (!isNaN(value)) {
parsed.value=parseFloat(value);
return parsed;
}
match = value.match(/([\-]?[0-9\.]+)(px|pt|%)/);
if (match) {
parsed.type = 'length';
parsed.value = parseFloat(match[1]);
parsed.unit = match[2];
return parsed;
}
match = match=value.match(/rgb\(([0-9]+),[ ]?([0-9]+),[ ]?([0-9]+)\)/);
if (match) {
parsed.type = 'color';
parsed.value = {
red:parseInt(match[1]),
green:parseInt(match[2]),
blue:parseInt(match[3]),
alpha:1
};
return parsed;
}
match=value.match(/rgba\(([0-9]+),[ ]?([0-9]+),[ ]?([0-9]+),[ ]?([\.0-9]+)\)/);
if (match) {
parsed.type = 'color';
parsed.value = {
red:parseInt(match[1]),
green:parseInt(match[2]),
blue:parseInt(match[3]),
alpha:parseFloat(match[4]),
};
return parsed;
}
var color = new hui.Color(value);
if (color.ok) {
parsed.type = 'color';
parsed.value = {
red:color.r,
green:color.g,
blue:color.b,
alpha:1
};
}
return parsed;
};
///////////////////////////// Item ///////////////////////////////
/**
* An animation item describing what to animate on an element
* @constructor
*/
hui.animation.Item = function(element) {
this.element = element;
this.work = [];
};
hui.animation.Item.prototype.animate = function(from,to,property,duration,delegate) {
var work = this.getWork(hui.string.camelize(property));
work.delegate = delegate;
work.finished = false;
var css = !(property=='scrollLeft' || property=='scrollTop' || property==='');
if (from!==null) {
work.from = from;
} else if (property=='transform') {
work.transform = hui.animation.Item.parseTransform(to,this.element);
} else if (!hui.browser.opacity && property=='opacity') {
work.from = this._getIEOpacity(this.element);
} else if (css) {
var style = hui.style.get(this.element,property);
var parsedStyle = hui.animation._parseStyle(style);
work.from = parsedStyle.value;
} else {
work.from = this.element[property];
}
if (css) {
var parsed = hui.animation._parseStyle(to);
work.to = parsed.value;
work.unit = parsed.unit;
if (!hui.browser.opacity && property=='opacity') {
work.updater = hui.animation._ieOpacityUpdater;
} else if (property=='transform') {
work.updater = hui.browser.msie ? function() {} : hui.animation._transformUpater;
} else if (parsed.type=='color') {
work.updater = hui.animation._colorUpater;
} else {
work.updater = hui.animation._lengthUpater;
}
} else {
work.to = to;
work.unit = null;
work.updater = hui.animation._propertyUpater;
}
work.start = Date.now();
if (delegate && delegate.delay) {
work.start+=delegate.delay;
}
work.end = work.start+duration;
hui.animation.start();
};
hui.animation.TRANSFORM = (function() {
var agent = navigator.userAgent;
var gecko = agent.indexOf('Gecko') !== -1 && agent.indexOf('WebKit') === -1;
return gecko ? 'MozTransform' : 'WebkitTransform';
})();
hui.animation.Item.parseTransform = function(value,element) {
var result = {};
var from,fromMatch;
var rotateReg = /rotate\(([0-9\.]+)([a-z]+)\)/i;
var rotate = value.match(rotateReg);
if (rotate) {
from = 0;
if (element.style[hui.animation.TRANSFORM]) {
fromMatch = element.style[hui.animation.TRANSFORM].match(rotateReg);
if (fromMatch) {
from = parseFloat(fromMatch[1]);
}
}
result.rotate = {from:from,to:parseFloat(rotate[1]),unit:rotate[2]};
}
var scaleReg = /scale\(([0-9\.]+)\)/i;
var scale = value.match(scaleReg);
if (scale) {
from = 1;
if (element.style[hui.animation.TRANSFORM]) {
fromMatch = element.style[hui.animation.TRANSFORM].match(scaleReg);
if (fromMatch) {
from = parseFloat(fromMatch[1]);
}
}
result.scale = {from:from,to:parseFloat(scale[1])};
}
return result;
};
hui.animation.Item.prototype._getIEOpacity = function(element) {
var filter = hui.style.get(element,'filter').toLowerCase();
var match = filter.match(/opacity=([0-9]+)/);
if (match) {
return parseFloat(match[1])/100;
} else {
return 1;
}
};
hui.animation.Item.prototype.getWork = function(property) {
for (var i = this.work.length - 1; i >= 0; i--) {
if (this.work[i].property===property) {
return this.work[i];
}
}
var work = {property:property};
this.work[this.work.length] = work;
return work;
};
/////////////////////////////// Loop ///////////////////////////////////
/** @constructor */
hui.animation.Loop = function(recipe) {
this.recipe = recipe;
this.position = -1;
this.running = false;
};
hui.animation.Loop.prototype.next = function() {
this.position++;
if (this.position>=this.recipe.length) {
this.position = 0;
}
var item = this.recipe[this.position];
if (typeof(item)=='function') {
item();
} else if (item.element) {
hui.animate(item.element,item.property,item.value,item.duration,{ease:item.ease});
}
var self = this;
var time = item.duration || 0;
if (item.wait!==undefined) {
time = item.wait;
}
window.setTimeout(function() {self.next();},time);
};
hui.animation.Loop.prototype.start = function() {
this.running=true;
this.next();
};
/** @namespace */
hui.ease = {
slowFastSlow : function(val) {
var a = 1.6;
var b = 1.4;
return -1*Math.pow(Math.cos((Math.PI/2)*Math.pow(val,a)),Math.pow(Math.PI,b))+1;
},
fastSlow : function(val) {
var a = 0.5;
var b = 0.7;
return -1*Math.pow(Math.cos((Math.PI/2)*Math.pow(val,a)),Math.pow(Math.PI,b))+1;
},
elastic : function(t) {
return 1 - hui.ease.elastic2(1-t);
},
elastic2 : function (t, a, p) {
if (t<=0 || t>=1) return t;
if (!p) p=0.45;
var s;
if (!a || a < 1) {
a=1;
s=p/4;
} else {
s = p/(2*Math.PI) * Math.asin (1/a);
}
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t-s)*(2*Math.PI)/p ));
},
bounce : function(t) {
if (t < (1/2.75)) {
return 7.5625*t*t;
} else if (t < (2/2.75)) {
return (7.5625*(t-=(1.5/2.75))*t + 0.75);
} else if (t < (2.5/2.75)) {
return (7.5625*(t-=(2.25/2.75))*t + 0.9375);
} else {
return (7.5625*(t-=(2.625/2.75))*t + 0.984375);
}
},
flicker : function(value) {
if (value==1) return 1;
return Math.random()*value;
},
quadIn: function(/* Decimal? */n){
return Math.pow(n, 2);
},
quadOut: function(/* Decimal? */n){
return n * (n-2) * -1;
},
quadInOut: function(/* Decimal? */n){
n=n*2;
if(n<1){ return Math.pow(n, 2) / 2; }
return -1 * ((--n)*(n-2) - 1) / 2;
},
cubicIn: function(/* Decimal? */n){
return Math.pow(n, 3);
},
cubicOut: function(/* Decimal? */n){
return Math.pow(n-1, 3) + 1;
},
cubicInOut: function(/* Decimal? */n){
n=n*2;
if(n<1){ return Math.pow(n, 3) / 2; }
n-=2;
return (Math.pow(n, 3) + 2) / 2;
},
quartIn: function(/* Decimal? */n){
return Math.pow(n, 4);
},
quartOut: function(/* Decimal? */n){
return -1 * (Math.pow(n-1, 4) - 1);
},
quartInOut: function(/* Decimal? */n){
n=n*2;
if(n<1){ return Math.pow(n, 4) / 2; }
n-=2;
return -1/2 * (Math.pow(n, 4) - 2);
},
quintIn: function(/* Decimal? */n){
return Math.pow(n, 5);
},
quintOut: function(/* Decimal? */n){
return Math.pow(n-1, 5) + 1;
},
quintInOut: function(/* Decimal? */n){
n=n*2;
if(n<1){ return Math.pow(n, 5) / 2; }
n-=2;
return (Math.pow(n, 5) + 2) / 2;
},
sineIn: function(/* Decimal? */n){
return -1 * Math.cos(n * (Math.PI/2)) + 1;
},
sineOut: function(/* Decimal? */n){
return Math.sin(n * (Math.PI/2));
},
sineInOut: function(/* Decimal? */n){
return -1 * (Math.cos(Math.PI*n) - 1) / 2;
},
expoIn: function(/* Decimal? */n){
return (n===0) ? 0 : Math.pow(2, 10 * (n - 1));
},
expoOut: function(/* Decimal? */n){
return (n==1) ? 1 : (-1 * Math.pow(2, -10 * n) + 1);
},
expoInOut: function(/* Decimal? */n){
if (n===0) { return 0; }
if (n===1) { return 1; }
n = n*2;
if (n<1) { return Math.pow(2, 10 * (n-1)) / 2; }
--n;
return (-1 * Math.pow(2, -10 * n) + 2) / 2;
},
circIn: function(/* Decimal? */n){
return -1 * (Math.sqrt(1 - Math.pow(n, 2)) - 1);
},
circOut: function(/* Decimal? */n){
n = n-1;
return Math.sqrt(1 - Math.pow(n, 2));
},
circInOut: function(/* Decimal? */n){
n = n*2;
if(n<1){ return -1/2 * (Math.sqrt(1 - Math.pow(n, 2)) - 1); }
n-=2;
return 1/2 * (Math.sqrt(1 - Math.pow(n, 2)) + 1);
},
backIn: function(/* Decimal? */n){
var s = 1.70158;
return Math.pow(n, 2) * ((s+1)*n - s);
},
backOut: function(/* Decimal? */n){
// summary: an easing function that pops past the range briefly, and
// slowly comes back.
n = n - 1;
var s = 1.70158;
return Math.pow(n, 2) * ((s + 1) * n + s) + 1;
},
backInOut: function(/* Decimal? */n){
var s = 1.70158 * 1.525;
n = n*2;
if(n < 1){ return (Math.pow(n, 2)*((s+1)*n - s))/2; }
n-=2;
return (Math.pow(n, 2)*((s+1)*n + s) + 2)/2;
},
elasticIn: function(/* Decimal? */n){
if(n===0){ return 0; }
if(n==1){ return 1; }
var p = 0.3;
var s = p/4;
n = n - 1;
return -1 * Math.pow(2,10*n) * Math.sin((n-s)*(2*Math.PI)/p);
},
elasticOut: function(/* Decimal? */n){
// summary: An easing function that elasticly snaps around the target value, near the end of the Animation
if(n===0) return 0;
if(n==1) return 1;
var p = 0.3;
var s = p/4;
return Math.pow(2,-10*n) * Math.sin((n-s)*(2*Math.PI)/p) + 1;
},
elasticInOut: function(/* Decimal? */n){
// summary: An easing function that elasticly snaps around the value, near the beginning and end of the Animation
if(n===0) return 0;
n = n*2;
if(n==2) return 1;
var p = 0.3*1.5;
var s = p/4;
if(n<1){
n-=1;
return -0.5*(Math.pow(2,10*n) * Math.sin((n-s)*(2*Math.PI)/p));
}
n-=1;
return 0.5 * (Math.pow(2, -10 * n) * Math.sin((n - s) * ( 2 * Math.PI) / p)) + 1;
},
bounceIn: function(/* Decimal? */n){
// summary: An easing function that "bounces" near the beginning of an Animation
return (1 - hui.ease.bounceOut(1-n)); // Decimal
},
bounceOut: function(/* Decimal? */n){
// summary: An easing function that "bounces" near the end of an Animation
var s=7.5625;
var p=2.75;
var l;
if(n < (1 / p)){
l = s*Math.pow(n, 2);
}else if(n < (2 / p)){
n -= (1.5 / p);
l = s * Math.pow(n, 2) + 0.75;
}else if(n < (2.5 / p)){
n -= (2.25 / p);
l = s * Math.pow(n, 2) + 0.9375;
}else{
n -= (2.625 / p);
l = s * Math.pow(n, 2) + 0.984375;
}
return l;
},
bounceInOut: function(/* Decimal? */n){
if(n<0.5){ return hui.ease.bounceIn(n*2) / 2; }
return (hui.ease.bounceOut(n*2-1) / 2) + 0.5; // Decimal
}
};
if (!Date.now) {
Date.now = function now() {
return new Date().getTime();
};
}
hui.on(function() {
hui.define('hui.animation',hui.animation);
});