动画原理
所谓的动画,就是通过一些列的运动形成的动的画面。在网页中,我们可以通过不断的改变元素的css值,来达到动的效果。
用到的公式
总距离S = 总时间T * 速度V 即: V = S/T
当前距离s = S/T * 已耗时t 即: s = S * (t/T)
即:当前距离 = 总距离 * (已耗时/总时间)
即:动画元素开始值 + (动画元素结束值 - 动画元素开始值) * (当前时间-开始时间) / (动画需要时间) + 值的格式
有了上面这些公式,我们就能利用javascript的setInterval或者setTimeout来做一个简单的动画了。
然而想要做一个动画库,就不得不考虑另外一些因素了。 比如同一个元素的动画,必须要有顺序的执行。不同元素的动画可以同步运行。
如此一来,就必须得用另外一个对象来管理这些动画了。我开始的想法是讲每个元素都放在一个数组里,用几个setInterval来循环取出数组中的动画函数依次执行。
animate1 = [{elem,fn},{elem,fn}];
animate2 = [{elem,fn},{elem,fn}];
这样就能达到,相同的元素动画,是有顺序的执行,而不同的则可以同时运行了。然后这样却存在一个问题,那就是如果超过10个元素的动画。程序就要开十个setInterval。
为了避免这样的情况发生,就在上面的基础上做了一些改进。使得,不论多少个动画。都使用一个setInterval来完成。修改后结构如下。
[ [elem,[fn,fn,fn,fn]], [elem,[fn,fn,fn,fn]], [elem,[fn,fn,fn,fn]] ]
这样一来,就可以用一个setInterval来完成所有动画了。 所需要做就是,循环取出elem,并执行elem后面一个元素的头一个fn,fn执行完毕后删除fn。调用下一个fn,如果fn全部为空则从大的数组中删除elem,如果elem为空时,则清楚setInterval。这样一来,逻辑上便可以走得通了。
然而动画最关键的因素还有一个,那就是缓动。 如果没有缓动,那么动画效果看起来就非常的死板。千篇一律。目前做js动画用到的缓动算法是很多的,大致分为两类。
一种是flash类,一种是prototype类。
flash的需要四个参数。分别是,
1.时间初始话的时间t
2.动画的初始值b
3.动画的结束值c
4.动画持续的时间d
下面是一个flash 类的匀速运动算法
Linear: function(t,b,c,d){ return c*t/d + b; }
另一种则是prototype,这一类的参数只需要一个,那就是当前时间t与持续时间d的比值 (t/d)
我采用了第二种,因为它的参数方便。也更加适合上面的动画公式,下面是一个prototype类的匀速运动算法
linear : function(t){ return t;}.
加入缓动后上面的公式变为
动画元素开始值 + (动画元素结束值 - 动画元素开始值) * 缓动函数((当前时间-开始时间) / (动画需要时间)) + 值的格式。
至此便是整个动画类设计便结束了。其中参考了一些其它人的博客,在此表示感谢!
最后,还是贴一下详细代码吧。
/** * create time 2012/08/29 * @author lynx cat. * @version 0.77beta. */ (function(win,doc){ var win = win || window; var doc = doc || win.document, pow = Math.pow, sin = Math.sin, PI = Math.PI, BACK_CONST = 1.70158; var Easing = { // 匀速运动 linear : function(t){ return t; }, easeIn : function (t) { return t * t; }, easeOut : function (t) { return ( 2 - t) * t; }, easeBoth : function (t) { return (t *= 2) < 1 ? .5 * t * t : .5 * (1 - (--t) * (t - 2)); }, easeInStrong : function (t) { return t * t * t * t; }, easeOutStrong : function (t) { return 1 - (--t) * t * t * t; }, easeBothStrong: function (t) { return (t *= 2) < 1 ? .5 * t * t * t * t : .5 * (2 - (t -= 2) * t * t * t); }, easeOutQuart : function(t){ return -(pow((t-1), 4) -1) }, easeInOutExpo : function(t){ if(t===0) return 0; if(t===1) return 1; if((t/=0.5) < 1) return 0.5 * pow(2,10 * (t-1)); return 0.5 * (-pow(2, -10 * --t) + 2); }, easeOutExpo : function(t){ return (t===1) ? 1 : -pow(2, -10 * t) + 1; }, swingFrom : function(t) { return t*t*((BACK_CONST+1)*t - BACK_CONST); }, swingTo: function(t) { return (t-=1)*t*((BACK_CONST+1)*t + BACK_CONST) + 1; }, sinusoidal : function(t) { return (-Math.cos(t*PI)/2) + 0.5; }, flicker : function(t) { var t = t + (Math.random()-0.5)/5; return this.sinusoidal(t < 0 ? 0 : t > 1 ? 1 : t); }, backIn : function (t) { if (t === 1) t -= .001; return t * t * ((BACK_CONST + 1) * t - BACK_CONST); }, backOut : function (t) { return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1; }, bounce : function (t) { var s = 7.5625, r; if (t < (1 / 2.75)) { r = s * t * t; } else if (t < (2 / 2.75)) { r = s * (t -= (1.5 / 2.75)) * t + .75; } else if (t < (2.5 / 2.75)) { r = s * (t -= (2.25 / 2.75)) * t + .9375; } else { r = s * (t -= (2.625 / 2.75)) * t + .984375; } return r; } }; /** * 基石 用于返回一个包含对话方法的对象 * @param elem * @return {Object} */ function catfx(elem){ elem = typeof elem === 'string' ? doc.getElementById(elem) : elem; return new fx(elem); } /** * 内部基石 用于返回一个包含对话方法的对象 * @param elem * @return {Object} */ function fx(elem){ this.elem = elem; return this; } /** * 基础类 包含一些基础方法,和不变量 */ var fxBase = { speed : { slow : 600, fast : 200, defaults : 400 }, fxAttrs : [], fxMap:[], /** * 返回对象元素的css值 * @param elem * @param p * @return css value */ getStyle : function(){ var fn = function (){}; if('getComputedStyle' in win){ fn = function(elem, p){ var p = p.replace(/\-(\w)/g,function(i,str){ return str.toUpperCase(); }); var val = getComputedStyle(elem, null)[p]; if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){ val = '0px'; } return val; } }else { fn = function(elem, p){ var p = p.replace(/\-(\w)/g,function(i,str){ return str.toUpperCase(); }); var val = elem.currentStyle[p]; if(~(' '+p+' ').indexOf(' width height') && val === 'auto'){ var rect = elem.getBoundingClientRect(); val = ( p === 'width' ? rect.right - rect.left : rect.bottom - rect.top ) + 'px'; } if(p === 'opacity'){ var filter = elem.currentStyle.filter; if( /opacity/.test(filter) ){ val = filter.match( /\d+/ )[0] / 100; val = (val === 1 || val === 0) ? val.toFixed(0) : val.toFixed(1); }else if( val === undefined ){ val = 1; } } if(~(' '+p+' ').indexOf(' left right top bottom ') && val === 'auto'){ val = '0px'; } return val; } } return fn; }(), /** * 返回对象元素的css值 * @param 颜色值(暂不支持red,pink,blue等英文) * @return rgb(x,x,x) */ getColor : function(val){ var r, g, b; if(/rgb/.test(val)){ var arr = val.match(/\d+/g); r = arr[0]; g = arr[1]; b = arr[2]; }else if(/#/.test(val)){ var len = val.length; if( len === 7 ){ r = parseInt( val.slice(1, 3), 16); g = parseInt( val.slice(3, 5), 16); b = parseInt( val.slice(5), 16); } else if( len === 4 ){ r = parseInt(val.charAt(1) + val.charAt(1), 16); g = parseInt(val.charAt(2) + val.charAt(2), 16); b = parseInt(val.charAt(3) + val.charAt(3), 16); } }else{ return val; } return { r : parseFloat(r), g : parseFloat(g), b : parseFloat(b) } }, /** * 返回解析后的css * @param prop * @return {val:val,unit:unit} */ parseStyle : function(prop){ var val = parseFloat(prop), unit = prop.replace(/^[\-\d\.]+/, ''); if(isNaN(val)){ val = this.getColor(unit); unit = ''; } return {val : val, unit : unit}; }, /** * 设置元素的透明度 * @param elem * @param val */ setOpacity : function(elem, val){ if( 'getComputedStyle' in win ){ elem.style.opacity = val === 1 ? '' : val; }else{ elem.style.zoom = 1; elem.style.filter = val === 1 ? '' : 'alpha(opacity=' + val * 100 + ')'; } }, /** * 设置元素的css值 * @param elem * @param prop * @param val */ setStyle : function(elem, prop, val){ if(prop != 'opacity'){ prop = prop.replace(/\-(\w)/g,function(i,p){ return p.toUpperCase(); }); elem.style[prop] = val; }else{ this.setOpacity(elem, val); } }, /** * 返回解析后的prop * @param prop * @return {prop} */ parseProp : function(prop){ var props = {}; for(var i in prop){ props[i] = this.parseStyle(prop[i].toString()); } return props; }, /** * 修正用户的参数 * @param elem * @param duration * @param easing * @param callback * @return {options} */ setOption : function(elem,duration, easing, callback){ var options = {}; var _this = this; options.duration = function(duration){ if(typeof duration == 'number'){ return duration; }else if(typeof duration == 'string' && _this.speed[duration]){ return _this.speed[duration]; }else{ return _this.speed.defaults; } }(duration); options.easing = function(easing){ if(typeof easing == 'function'){ return easing; }else if(typeof easing == 'string' && Easing[easing]){ return Easing[easing]; }else{ return Easing.linear; } }(easing); options.callback = function(callback){ var _this = this; return function (){ if(typeof callback == 'function'){ callback.call(elem); } } }(callback) return options; }, /** * 维护setInterval的函数,动画的启动 */ tick : function(){ var _this = this; if(!_this.timer){ _this.timer = setInterval(function(){ for(var i = 0, len = _this.fxMap.length; i < len; i++){ var elem = _this.fxMap[i][0]; var core = _this.data(elem)[0]; core(elem); } },16); } }, /** * 停止所有动画 */ stop : function(){ if(this.timer){ clearInterval(this.timer); this.timer = undefined; } }, /** * 存储或者拿出队列 * @param elem */ data : function(elem){ for(var i = 0, len = this.fxMap.length; i < len; i++){ var data = this.fxMap[i]; if(elem === data[0]){ return data[1]; } } this.fxMap.push([elem,[]]); return this.fxMap[this.fxMap.length - 1][1]; }, /** * 删除队列 * @param elem */ removeData : function(elem){ for(var i = 0, len = this.fxMap.length; i < len; i++){ var data = this.fxMap[i]; if(elem === data[0]){ this.fxMap.splice(i, 1); if(this.isDataEmpty()){ this.stop(); } } } }, isDataEmpty : function(){ return this.fxMap.length == 0; } }, $ = fxBase; /** * 核心对象,用于生成动画对象。 * @param elem * @param props * @param options * @return {Object} */ function fxCore(elem, props, options){ this.elem = elem; this.props = props; this.options = options; this.start(); } fxCore.prototype = { constructor : fxCore, /** * 将动画函数加入到队列中,并启动动画。 */ start : function(){ var cores = $.data(this.elem); cores.push(this.step()); $.tick(); }, /** * 核心方法,控制每一帧元素的状态。 * @return function */ step : function(){ var _this = this; var fn = function(elem){ var t = Date.now() - this.startTime; if(Date.now() < this.startTime + this.options.duration){ if(t <= 1){ t = 1;} for(var i in this.target){ if(typeof this.source[i]['val'] === 'number'){ var val = parseFloat((this.source[i]['val'] + (this.target[i]['val'] - this.source[i]['val']) * this.options.easing(t / this.options.duration)).toFixed(7)); }else{ var r = parseInt(this.source[i]['val']['r'] + (this.target[i]['val']['r'] - this.source[i]['val']['r']) * this.options.easing(t / this.options.duration)); var g = parseInt(this.source[i]['val']['g'] + (this.target[i]['val']['g'] - this.source[i]['val']['g']) * this.options.easing(t / this.options.duration)); var b = parseInt(this.source[i]['val']['b'] + (this.target[i]['val']['b'] - this.source[i]['val']['b']) * this.options.easing(t / this.options.duration)); var val = 'rgb(' + r + ',' + g + ',' + b + ')'; } $.setStyle(this.elem,i,val + this.source[i]['unit']); } }else{ for(var i in this.target){ if(typeof this.target[i]['val'] === 'number'){ var val = this.target[i]['val']; }else{ var val = 'rgb(' + this.target[i]['val']['r'] + ',' + this.target[i]['val']['g'] + ',' + this.target[i]['val']['b'] + ')'; } $.setStyle(elem,i,val + this.source[i]['unit']); } var cores = $.data(elem); cores.shift(); this.options.callback(); if(cores.length == 0){ $.setStyle(elem,'overflow',this.overflow); $.removeData(elem); } } } return function(elem){ if(!_this.startTime){ var source = {}; _this.target = _this.props; for(var i in _this.props){ var val = $.getStyle(_this.elem, i); source[i] = $.parseStyle(val); } _this.source = source; _this.startTime = Date.now(); _this.overflow = $.getStyle(elem,'overflow'); $.setStyle(elem,'overflow','hidden'); } fn.call(_this,elem); } } } /** * 外部接口类。 */ fx.prototype = { constructor : fx, /** * 动画方法 * @param prop * @param duration * @param easing * @param callback * @return {Object} */ animate : function(prop, duration, easing, callback){ if(arguments.length == 3 && typeof easing === 'function'){ //多数时候用户第三个参数是回调 callback = easing; easing = undefined; } var props = $.parseProp(prop); var options = $.setOption(this.elem,duration,easing,callback); var core = new fxCore(this.elem,props,options); return this; }, /** * 停止动画方法 * 使用方法 catjs('your element id').stop(); */ stop : function(){ $.removeData(this.elem); } } win.catfx = catfx; })(this,document);
使用起来也比较简单.直接catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,'easeOut',function(){});
跟jquery的使用方法差不多,如果不传第二个参数,则默认为400毫秒。不传第三个参数则默认匀速。第三个参数为函数,并且总共只有三个参数时。第三个参数为回调。
例:catfx('ID').animate({'margin-left':200,'background-color':'#ff0000'},600,function(){alert('洒家是回调函数~')});
javascript动画浅析
声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
Reply on: @reply_date@
@reply_contents@