简化代码学习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方法。还有很多,这些全部加起来,源码就变得挺复杂了~

分类:技术文章 Tags:
评论

2010年8月27日 14:47

为什么不把iQuery做为jQuery的扩展呢?

2010年8月27日 15:36

@迅即的风 因为iquery本来就是为了简化jquery,删除jquery兼容浏览器部分的代码,提高效率减小体积

2010年9月6日 10:45

点那个stop真难啊…

2013年2月19日 17:19

刚才一口气看了楼主博客的很多文章,非常佩服你时刻保持奋斗的那种心态。想想自己也毕业半年啦,但是由于基础比较差,一直没能做出一个属于自己的产品。