简化代码学习jquery动画源码
2010-8-18
效果见这里:http://bangswork.googlecode.com/svn/trunk/lab/effect/index.html
jquery的动画总体思路是:
有一个fx类专门处理动画,fx的各个实例共享一个timers数组和一个setInterval。对每个传进来的dom的每个属性值都新建一个fx实例去处理,一个fx实例对应一个dom的一个属性的变化。fx里有个step函数,可以计算出当前这个时刻这个属性要达到什么值。
这个step会通过共用的一个setInterval每13毫秒执行一次,这就可以使得它行成动画。另外如果浏览器速度太慢无法达到13毫秒执行一次step,动画也会按时完成,因为是根据当前系统时间计算属性要达到的值的。
每一个fx实例的step都会放进timers数组,实际上setInterval是持续执行timers里的每一个函数,这样只用一个setInterval就让众多属性“一起动”了。step里判断到超过了动画运行的时间,就会返回false让它从timers里移除,timers为空时clearInterval。
下面简单实现这整个过程:
(function (window) { var timerId, timers = [], rfxnum = /^([+-]=)?([\d+-.]+)(.*)$/, effect = { animate : function (elems, prop, duration) { for ( var i = 0, l = elems.length; i < l; i ++ ) { for ( var p in prop ) { var e = new effect.fx ( elems[i], p, prop[p], duration ), parts = rfxnum.exec(prop[p]), start = e.cur(), //获得属性初始状态 end = parseFloat( parts[2] ), unit = p == "opacity" ? "" : parts[3] || "px"; e.custom( start, end, unit ); } } }, now : function () { return new Date().getTime(); }, stop : function (elems) { //stop清除timers里于传进的elems有关的元素 for ( var i = 0, l = elems.length; i < l; i ++ ) { for ( var j = timers.length - 1; j >= 0; j-- ) { if ( timers[j].elem === elems[i] ) { timers.splice(j, 1); } } }; }, easing: { //线性变化公式 linear: function( p, n, firstNum, diff ) { return firstNum + diff * p; }, //缓动公式 swing: function( p, n, firstNum, diff ) { return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; } }, fx : function (elem, name, val, duration) { this.val = val; this.duration = duration; this.elem = elem; this.name = name; } } effect.fx.prototype = { cur : function () { return parseFloat( this.elem.style[this.name] ); }, custom : function (from, to, unit) { this.startTime = effect.now(); this.start = from; this.end = to; this.unit = unit; this.now = this.start; this.state = this.pos= 0; //如果是线性变化 state会一直等于pos 如果是缓动 会通过state计算出pos 即此刻应该达到几分之几的状态 //为了让step里的this指向正确,创建一个闭包 var self = this; function t() { return self.step(); } //指明这个函数对应的dom元素,在stop里可以根据这个元素指定特定dom停止动画 t.elem = this.elem timers.push(t) if ( !timerId ) { timerId = setInterval( effect.fx.tick, 13 ); } }, step : function () { var t = effect.now(), done = true; if ( t >= this.duration + this.startTime ) { this.now = this.end; this.state = this.pos = 1; this.update(); return false; } else { var n = t - this.startTime; this.state = n / this.duration; //如果只是线性渐变,这里可以直接this.pos = this.state this.pos = effect.easing.swing( this.state, n, 0, 1, this.duration); this.now = this.start + ( (this.end - this.start) * this.pos ); this.update(); return true; } }, update : function () { this.elem.style[this.name] = this.now + this.unit; } } effect.fx.tick = function () { for ( var i = 0; i < timers.length; i ++ ) { !timers[i]() && timers.splice( i--, 1 ); } !timers.length && effect.fx.stop(); } effect.fx.stop = function () { clearInterval( timerId ); timerId = null; } window.effect = effect; })(window);
这个是把整个过程尽量简化了,jquery做的包括很多细节处理,例如,不同形式的传参方式统一成包装一个对象,处理各种show hide,处理元素单位确保不出错,其中获取元素的width和height的值比较复杂,放在css模块里。还有支持队列的特性,默认是各种动画同步执行,如果需要按队列排序执行就用到queue方法。还有很多,这些全部加起来,源码就变得挺复杂了~
为什么不把iQuery做为jQuery的扩展呢?