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); });