jquery1.43源码分析之动画部分

js实现动画的原理跟动画片的制作一样.动画片是把一些差距不大的原画以一定帧数播放.js动画是靠连续改变元素的某个css属性值,比如left, top.达到视觉的动画效果.

这几年出现了不少优秀的js游戏, 比如前段时间的《js版植物大战僵尸》.其实js游戏主要就是这4个部分组成. 绘图, 移动, 碰撞检测, 逻辑设定.如果用jquery来做的话, 前三项都会变得相当容易.

去年我也用jquery写了2个小游戏(jquery坦克大战和jquery泡泡堂).也写了自己的动画类.不过当时限于水平,没有深入分析 jQuery.fx类的实现. 这几天读过源码之后, 感受很多. jquery的fx类虽然只有600多行代码.也没有牵涉到太高深的js知识.里面的逻辑却比较复杂,很多处理也很精妙.当真正披荆斩棘弄懂这一部分之 后,相信对javascript的理解都会新上一层,当然也会更加喜欢jquery.

闲话少说, 在看源码之前, 先大概了解一下fx类的实现思想.
首先fx类非常依赖jquery的队列机制,没有这个东西的话,一切都无从谈起.有关jquery队列机制, 见http://www.iteye.com/topic/783260 .
回忆一下这句代码
$(‘div’).show(1000).hide(1000);
让这个div在1000ms内渐渐显示,然后再渐渐隐藏. 这2个动画是按次序执行的.在javascript的单线程异步模式下,管理异步的函数是很难的.在时间戳上, 既无法知道它准确的开始时间,又不能得到它准确的结束时间. 由于可能发生线程阻塞, 这些时间并不精确. 而让它们有序的执行, 最好的办法就是把元素上所有的动画都放入队列. 当第一个动画结束后, 在回调函数里通知第二个动画执行.

好比有个公司招聘, 只有一个面试官,而有很多应聘者在排队等候, 一个人面试完之后,出门的时候顺便告诉第二个人进去面试.以此反复. 而作为面试官, 只需要通知第一个面试者.

对于jquery, 当你使用animate函数执行动画的时候,这个动画并没有马上被执行, 它会先存入元素的队列缓存里. 然后看是不是已经有正在执行的动画. 如果没有, 就取出队列里的第一个动画并且执行(此时队伍里可能还有别人, 只是被stop函数暂停了), 如果有,那么要等到队列前面的所有动画执行完之后才会被通知执行.

就好像, 现在来了一位应聘者, 他先看看是不是已经有人在里面面试. 如果没有, 那么他可以直接进去面试. 如果有, 他必须得加入到队伍的最后一个. 等前面的人全部面试完了才轮到他.

animate函数的主要功能并不是执行动画. 它只作为api的入口,修正参数.然后把参数扔给fx类去执行动画. jquery的动画模块也并没有细化得太离谱, 有几个方法是比较重要的.
jQuery.fn.animate  修正参数
jQuery.speed    静态方法, 帮助animate修正参数, 并且重写回调函数.重写回调函数大概就是
callback = function(){
callback();
$(this.dequeue());
}
jQuery.fx   构造函数, 跟动画有关的具体操作都在这个构造函数的原型方法里.
jQuery.fx.tick  静态方法, 作为定时器的方法, 监控所有动画的执行情况.

我们最好先抛开复杂的参数修正等枝枝叶叶.直接从主干部分下手.先分析一下这个要使得这个动画类基本够用, 需要一些什么条件.
1  至少需要一个定时器来执行动画, 而且最好只有一个,即使有多个动画需要同时执行. 毕竟setTimeout和setInterval的开销是巨大的.
2  需要一个队列机制来管理动画顺序, jquery已经提供了queue和dequeue.
3  需要一种算法来计算属性的当前值.比如当前位置,当前大小.
4  需要一个具体执行动画的函数.
对于1, 我们制造一个tick函数来设置定时器, 并且在定时器中观察动画的执行情况.
对于2  直接用jquery的队列机制就可以了
对于3  jquery提供了默认的swing和liner. 我们还可以用一些别的算法, 比如tween.
对于4  这个函数得自己构建.怎么构建随意.不过它最好还带上停止动画等功能.

好吧, 现在正式开始.我们不在一开始就钻进jquery的源码里去.先模仿jquery的思想. 自己实现一个动画fx类. 通过这个简化了的fx类, 再来反过头来了解jquery.


实现原理是这样, 修正参数后为每个属性的动画都生成一个动画对象fx.比如一个元素要改变left和top, 那么为left和top分别创建一个动画fx对象, 然后把这些fx对象都push到全局timers数组.在定时器里循环这些fx对象, 每隔一段时间把他们对应的元素属性重新绘制一帧.直到达到指定的动画持续时间.这时触发这次animate的callback函数.并且从全局timer 数组把元素的这些fx对象都清除出去.直到timers里没有fx对象,表示页面的动画全部结束,此时清空定时器.当然这中间少不了队列控制.


先定义一个数组和一个定时器. 数组用来装载页面上所有动画. 当数组里没有动画时, 表示所有动画执行完毕, 这时清掉定时器.
var timers = [];
var timerId;

然后是animate函数. 我们叫它myAnimate. 它传入4个参数. 分别是{"left":500}这样的property-value对象,动画持续时间, 动画算法, 回调函数.
myAnimate里调用一个getOpt函数. getOpt的作用是把参数组装成对象返回. 并且在回调函数里加上通知下一个动画执行的动作. 即.dequeue()

Java代码
  1. $.fn.extend({  
  2.     myAnimate: function(property, duration, easing, callback){  
  3.         var operate = jQuery.getOpt(duration, easing, callback);  
  4.     //得到一个包含了duration, easing, callback等参数的对象.并且callback已经被修正.   
  5.         $(this ).queue(function(){  
  6.     //把具体执行动画的函数放入队列, 注意这里如果队列中没有动画函数, 就直接执行这个匿名function了.   
  7.             var elem = this ;  
  8.             $.each(property, function(name, value){  
  9.           //遍历每个属性, 为每个属性的动画都生成一个fx对象.   
  10.                     var fx = new  FX(elem, operate, name);  
  11.                     var start = parseInt($(elem).css(name));  
  12.               //计算属性开始的值   
  13.                     var end = value;  
  14.               //属性结束的值   
  15.                     fx.custom(elem, start, end);  
  16.               //转交给FX的prototype方法cunstom执行动画   
  17.             })  
  18.         })  
  19.         return   this ;  
  20.       //返回this, 以便链式操作.   
  21.     }  
  22. })  
$.fn.extend({
	myAnimate: function(property, duration, easing, callback){
		var operate = jQuery.getOpt(duration, easing, callback);
	//得到一个包含了duration, easing, callback等参数的对象.并且callback已经被修正.
		$(this).queue(function(){
	//把具体执行动画的函数放入队列, 注意这里如果队列中没有动画函数, 就直接执行这个匿名function了.
			var elem = this;
			$.each(property, function(name, value){
          //遍历每个属性, 为每个属性的动画都生成一个fx对象.
					var fx = new FX(elem, operate, name);
					var start = parseInt($(elem).css(name));
              //计算属性开始的值
					var end = value;
              //属性结束的值
					fx.custom(elem, start, end);
              //转交给FX的prototype方法cunstom执行动画
			})
		})
		return this;
      //返回this, 以便链式操作.
	}
})


FX构造函数

Java代码
  1. function FX(elem, options, name){  
  2.     this .elem = elem;  
  3.     this .options = options;  
  4.     this .name = name;  
  5. }  
function FX(elem, options, name){
	this.elem = elem;
	this.options = options;
	this.name = name;
}


FX构造函数里只初始化3个实例属性.比如
this.elem =  elem.
this.options={"duration": 500, "easing":"swing", callback:fn}
this.name = "left"
其他属性留在后面动态生成.

custom 方法
custom方法的任务主要是把当前fx对象放入全局的timer,并且启动定时器来观察动画执行情况.

Java代码
  1. FX.prototype.custom = function(from, to){  
  2.     this .startTime = jQuery.now();  
  3.    //开始的时间, 和当前时间一起就可以计算已消耗时间.   
  4.    //再用已消耗时间和持续时间相比就知道动画是否结束.    
  5.     this .start = from;  
  6.     //属性初始的值   
  7.     this .end = to;  
  8.     //属性动画后的值   
  9.     timers.push(this );  
  10.    //把每个fx对象都push进全局的动画堆栈.   
  11.     FX.tick();  
  12.    //启动定时器.   
  13. }  
FX.prototype.custom = function(from, to){
	this.startTime = jQuery.now();
   //开始的时间, 和当前时间一起就可以计算已消耗时间.
   //再用已消耗时间和持续时间相比就知道动画是否结束. 
	this.start = from;
	//属性初始的值
	this.end = to;
	//属性动画后的值
	timers.push(this);
   //把每个fx对象都push进全局的动画堆栈.
	FX.tick();
   //启动定时器.
}


FX.tick
用来监控动画执行情况

Java代码
  1. FX.tick = function(){  
  2.         if  (timerId)  return ;  
  3.   //只需要一个定时器,所以如果该定时器已经存在了,直接return   
  4.         timerId = setInterval(function(){  
  5.             for  (var i =  0 , c; c = timers[i++];){  
  6.   //每隔13ms, 遍历timerId里的每个动画对象fx, 让它们执行下一步.   
  7.                 c.step();  
  8.             }  
  9.             if  (!timers.length){  
  10. //如果timers没有元素, 说明页面的所有动画都执行完毕, 清除定时器.   
  11. //这个全局定时器就像一个总考官, 它会每隔一段时间巡视每个考生,    
  12. //督促他们赶紧答题.   
  13. //如果考生全部考试完毕交卷了, 他会进入休息状态.   
  14. //这时如果又进来了考生, 他又进入工作状态.   
  15.                 FX.stop();  
  16.             }  
  17.         }, 13 );  
  18.     }  
FX.tick = function(){
		if (timerId) return;
  //只需要一个定时器,所以如果该定时器已经存在了,直接return
		timerId = setInterval(function(){
			for (var i = 0, c; c = timers[i++];){
  //每隔13ms, 遍历timerId里的每个动画对象fx, 让它们执行下一步.
				c.step();
			}
			if (!timers.length){
//如果timers没有元素, 说明页面的所有动画都执行完毕, 清除定时器.
//这个全局定时器就像一个总考官, 它会每隔一段时间巡视每个考生, 
//督促他们赶紧答题.
//如果考生全部考试完毕交卷了, 他会进入休息状态.
//这时如果又进来了考生, 他又进入工作状态.
				FX.stop();
			}
		}, 13);
	}


FX.stop
清空定时器

Java代码
  1. FX.stop = function(){  
  2.     clearInterval(timerId);   
  3.     timerId = null   
  4. }  
FX.stop = function(){
	clearInterval(timerId);	
	timerId = null
}



FX.prototype.step
执行每一步动画

Java代码
  1. FX.prototype.step = function(){  
  2.         var t = jQuery.now();  
  3.     //当前时间   
  4.         var nowPos;  
  5.     //当前属性值   
  6.         if  (t >  this .startTime +  this .options.duration){  
  7. //如果现在时间超过了开始时间 + 持续时间, 说明动画应该结束了   
  8.             nowPos = this .end;  
  9. //动画的确切执行时间总是13ms的倍数, 很难刚好等于要求的动画持续时间,所以一般可能会有小小的误差, 修正下动画最后的属性值.   
  10.             this .options.callback.call( this .elem);  
  11. //执行回调函数   
  12.             this .stop();  
  13. //把已经完成动画的任务fx对象从全局timer中删除.   
  14.         }else {  
  15.             var n = t - this .startTime;  
  16.  //动画已消耗的时间   
  17.             var state = n / this .options.duration;        
  18.     var pos = jQuery.easing[this .options.easing](state, n,  01this .options.duration);  
  19.             nowPos = this .start + (( this .end -  this .start) * pos);  
  20.         }  
  21.    //根据时间比和easing算法, 算出属性的当前值   
  22.             this .update(nowPos,  this .name);  
  23.    //给属性设置值   
  24.     }  
FX.prototype.step = function(){
		var t = jQuery.now();
	//当前时间
		var nowPos;
	//当前属性值
		if (t > this.startTime + this.options.duration){
//如果现在时间超过了开始时间 + 持续时间, 说明动画应该结束了
			nowPos = this.end;
//动画的确切执行时间总是13ms的倍数, 很难刚好等于要求的动画持续时间,所以一般可能会有小小的误差, 修正下动画最后的属性值.
			this.options.callback.call(this.elem);
//执行回调函数
			this.stop();
//把已经完成动画的任务fx对象从全局timer中删除.
		}else{
			var n = t - this.startTime;
 //动画已消耗的时间
			var state = n / this.options.duration;		
	var pos = jQuery.easing[this.options.easing](state, n, 0, 1, this.options.duration);
			nowPos = this.start + ((this.end - this.start) * pos);
		}
   //根据时间比和easing算法, 算出属性的当前值
			this.update(nowPos, this.name);
   //给属性设置值
	}


FX.prototype.stop

从全局timer中删除一个已经完成动画任务的fx对象.

Java代码
  1. FX.prototype.stop = function(){  
  2.     for  ( var i = timers.length -  1 ; i >=  0 ; i--){  
  3.         if  (timers[i] ===  this ){  
  4.             timers.splice(i, 1 );  
  5.         }  
  6.     }  
  7. }  
	FX.prototype.stop = function(){
		for ( var i = timers.length - 1; i >= 0; i--){
			if (timers[i] === this){
				timers.splice(i, 1);
			}
		}
	}


FX.prototype.update
给属性设置值

Java代码
  1.       
  2. FX.prototype.update = function(value, name){  
  3.         this .elem.style[name] = value;  
  4.     }  
	
FX.prototype.update = function(value, name){
		this.elem.style[name] = value;
	}


注意FX.stop和FX.prototype.stop是不同的. 前者是取消定时器, 这样页面的所有动画都会结束. 后者是停止某个fx对象的动画.比如left, opacity.

下面是可供测试的全部代码

Java代码
  1. <style type= "text/css" >  
  2.   
  3. #div1 {  
  4.     background:#aaa;  
  5.     width:188px;  
  6.     height:188px;  
  7.     position:absolute;  
  8.     top:10px;  
  9.     left: 110px;  
  10. }  
  11.   
  12. #div2 {  
  13.     background:#aaa;  
  14.     width:188px;  
  15.     height:188px;  
  16.     position:absolute;  
  17.     top:310px;  
  18.     left: 110px;  
  19. }  
  20.   
  21. </style>   
  22.   
  23. <body>   
  24. </body>  
  25.   
  26. <div id="div1" >我是一个div</div>  
  27. <div id="div2" >我是另一个div</div>  
  28.   
  29. <script type="text/javascript"  src= "jquery1.43.js" ></script>   
  30. <script type="text/javascript" >  
  31.       
  32.   
  33. var timers = [];  
  34. var timerId;  
  35.   
  36.   
  37. $.fn.extend({  
  38.     myAnimate: function(property, duration, easing, callback){  
  39.         var operate = jQuery.getOpt(duration, easing, callback);  
  40.         $(this ).queue(function(){  
  41.             var elem = this ;  
  42.             $.each(property, function(name, value){  
  43.                     var fx = new  FX(elem, operate, name);  
  44.                     var start = parseInt($(elem).css(name));  
  45.                     var end = value;  
  46.                     fx.custom(start, end);  
  47.             })  
  48.         })  
  49.         return   this ;  
  50.     }  
  51. })  
  52.   
  53. function FX(elem, options, name){  
  54.     this .elem = elem;  
  55.     this .options = options;  
  56.     this .name = name;  
  57. }  
  58.       
  59. FX.prototype.custom = function(from, to){  
  60.     this .startTime = jQuery.now();  
  61.     this .start = from;  
  62.     this .end = to;  
  63.     timers.push(this );  
  64.     FX.tick();  
  65. }  
  66.   
  67. FX.prototype.step = function(){  
  68.         var t = jQuery.now();  
  69.         var nowPos;  
  70.         if  (t >  this .startTime +  this .options.duration){  
  71.             nowPos = this .end;  
  72.             this .options.callback.call( this .elem);  
  73.             this .stop();  
  74.         }else {  
  75.             var n = t - this .startTime;  
  76.             var state = n / this .options.duration;  
  77.             var pos = jQuery.easing[this .options.easing](state, n,  01this .options.duration);  
  78.             nowPos = this .start + (( this .end -  this .start) * pos);  
  79.         }  
  80.             this .update(nowPos,  this .name);  
  81.     }  
  82.   
  83.     FX.prototype.stop = function(){  
  84.         for  ( var i = timers.length -  1 ; i >=  0 ; i--){  
  85.             if  (timers[i] ===  this ){  
  86.                 timers.splice(i, 1 );  
  87.             }  
  88.         }  
  89.     }  
  90.   
  91.     FX.prototype.update = function(value, name){  
  92.         this .elem.style[name] = value;  
  93.     }  
  94.   
  95.   
  96.     FX.tick = function(){  
  97.         if  (timerId)  return ;  
  98.         var self = this ;  
  99.         timerId = setInterval(function(){  
  100.             for  (var i =  0 , c; c = timers[i++];){  
  101.                 c.step();  
  102.             }  
  103.             if  (!timers.length){  
  104.                 FX.stop();  
  105.             }  
  106.         }, 13 );  
  107.     }  
  108.       
  109.     FX.stop = function(){  
  110.         clearInterval(timerId);   
  111.         timerId = null   
  112.     }  
  113.   
  114. jQuery.getOpt = function(duration, easing, callback){  
  115.       
  116.     var obj = {  
  117.         "duration" : duration,  
  118.         "easing" : easing  
  119.     }  
  120.     
  121.     obj.callback = function(){  
  122.         callback && callback();  
  123.         $(this ).dequeue();    
  124.     }  
  125.       
  126.     return  obj;  
  127. }  
  128.   
  129. $.fn.stop = function(){  
  130.     for  ( var i = timers.length -  1 ; i >=  0 ; i-- ) {  
  131.         if  (timers[i].elem ===  this [ 0 ]){  
  132.             timers[i].stop();     
  133.         }         
  134.     }  
  135. }  
  136.   
  137.   
  138.   
  139. $("#div1" ).myAnimate({ "top" : 500 },  1000"swing" ).myAnimate({ "top" : 100 },  500"swing" ).myAnimate({ "left" : 500 },  500"swing" ).myAnimate({ "top" : 500 },  500"swing" );  
  140.   
  141. $("#div2" ).myAnimate({ "left" : 1000 },  1000"swing" )  
  142.   
  143.   
  144. function stop(){  
  145.     $("#div1" ).stop();    
  146. }  
  147.   
  148. function cont(){  
  149.     $("#div1" ).dequeue();     
  150. }  
  151.   
  152. </script>   
  153. <button οnclick="stop()" >停止</button>  
  154. <button οnclick="cont()" >继续后面的动画</button>  
<style type="text/css">

#div1 {
	background:#aaa;
	width:188px;
	height:188px;
	position:absolute;
	top:10px;
	left: 110px;
}

#div2 {
	background:#aaa;
	width:188px;
	height:188px;
	position:absolute;
	top:310px;
	left: 110px;
}

</style> 

<body> 
</body>

<div id="div1">我是一个div</div>
<div id="div2">我是另一个div</div>

<script type="text/javascript" src="jquery1.43.js"></script> 
<script type="text/javascript">
	

var timers = [];
var timerId;


$.fn.extend({
	myAnimate: function(property, duration, easing, callback){
		var operate = jQuery.getOpt(duration, easing, callback);
		$(this).queue(function(){
			var elem = this;
			$.each(property, function(name, value){
					var fx = new FX(elem, operate, name);
					var start = parseInt($(elem).css(name));
					var end = value;
					fx.custom(start, end);
			})
		})
		return this;
	}
})

function FX(elem, options, name){
	this.elem = elem;
	this.options = options;
	this.name = name;
}
	
FX.prototype.custom = function(from, to){
	this.startTime = jQuery.now();
	this.start = from;
	this.end = to;
	timers.push(this);
	FX.tick();
}

FX.prototype.step = function(){
		var t = jQuery.now();
		var nowPos;
		if (t > this.startTime + this.options.duration){
			nowPos = this.end;
			this.options.callback.call(this.elem);
			this.stop();
		}else{
			var n = t - this.startTime;
			var state = n / this.options.duration;
			var pos = jQuery.easing[this.options.easing](state, n, 0, 1, this.options.duration);
			nowPos = this.start + ((this.end - this.start) * pos);
		}
			this.update(nowPos, this.name);
	}

	FX.prototype.stop = function(){
		for ( var i = timers.length - 1; i >= 0; i--){
			if (timers[i] === this){
				timers.splice(i, 1);
			}
		}
	}

	FX.prototype.update = function(value, name){
		this.elem.style[name] = value;
	}


	FX.tick = function(){
		if (timerId) return;
		var self = this;
		timerId = setInterval(function(){
			for (var i = 0, c; c = timers[i++];){
				c.step();
			}
			if (!timers.length){
				FX.stop();
			}
		}, 13);
	}
	
	FX.stop = function(){
		clearInterval(timerId);	
		timerId = null
	}

jQuery.getOpt = function(duration, easing, callback){
	
	var obj = {
		"duration": duration,
		"easing": easing
	}
  
	obj.callback = function(){
		callback && callback();
		$(this).dequeue();	
	}
	
	return obj;
}

$.fn.stop = function(){
	for ( var i = timers.length - 1; i >= 0; i-- ) {
		if (timers[i].elem === this[0]){
			timers[i].stop();	
		}		
	}
}



$("#div1").myAnimate({"top":500}, 1000, "swing").myAnimate({"top":100}, 500, "swing").myAnimate({"left":500}, 500, "swing").myAnimate({"top":500}, 500, "swing");

$("#div2").myAnimate({"left":1000}, 1000, "swing")


function stop(){
	$("#div1").stop();	
}

function cont(){
	$("#div1").dequeue();	
}

</script> 
<button οnclick="stop()">停止</button>
<button οnclick="cont()">继续后面的动画</button>


--------------------分割线---------------------------------

上面的代码跟jquery有少许不同, 因为我写的时候有的地方忘记jquery是怎么搞的了.不过思路还是一样的.
尽管这样, jquery的做法要麻烦很多. 毕竟作为一个库, 要考虑的东西是非常非常多的.

一行一行来看代码.
首先定义一个常量

Java代码
  1. fxAttrs = [  
  2.         // height animations   
  3.         [ "height""marginTop""marginBottom""paddingTop""paddingBottom"  ],  
  4.         // width animations   
  5.         [ "width""marginLeft""marginRight""paddingLeft""paddingRight"  ],  
  6.         // opacity animations   
  7.         [ "opacity"  ]  
  8.     ]  
fxAttrs = [
		// height animations
		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
		// width animations
		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
		// opacity animations
		[ "opacity" ]
	]


这个东西是为了得到当用动画形式执行show,hide,slideDown, slideUp时,所需要改变的属性值.比如show,需要改变的并不仅仅是width和height.还包括marginLeft,paddingLeft等属性.

jQuery.prototype.show
show和hide这两个常用的方法实现都比较简单. show和hide可以直接隐藏/显示元素,也可以以动画渐变的方式来达到效果.直接隐藏和显示就是设置display属性,动画效果则要用 animate函数改变width,height,marginLeft等.不过设置display的时候还要考虑css属性对元素可见性的影响,以及尽 量避免reflow回流.留在css部分讨论.

jQuery.prototype.toggle
切换元素的可见状态, 就是show和hide这两个操作集中到一起.
toggle有好几种调用方式,
1  不传递任何参数, 这时仅仅只切换元素的可见状态
2  只有一个boolean类型的参数. 这个参数实际是一个返回值为boolean的表达式. 当这个表达式结果为true的时候显示元素, 反之隐藏. 即toggle(switch)形式. 比如
var flip = 0;   
$("button").click(function () {
        $("p").toggle( flip++ % 3 == 0 );   
});
3  传入一些函数, 当点击元素的时候, 按顺序执行这些函数的某一个.

4  可以传入3个参数, 分别为speed, easing, callback. 即以动画的形式切换可见性.

这几个方法的源码都跟动画的核心机制关系不大, 反而是和css, evnet模块联系比较密切. 就不放在这里讨论了.

现在看看关键的animate函数.前面讲过, animate的作用主要是修正参数, 把参数传递给fx类去实现动画.

Java代码
  1. animate: function( prop, speed, easing, callback ) {  
  2.         //prop是{"left":"100px", "paddingLeft": "show"}这种形式,可以由用户自己传进来, 在show,   hide等方法里, 是由genfx   
  3. //函数转化而来.    
  4.         var optall = jQuery.speed(speed, easing, callback);  
  5.         //optall是一个包含了修正后的动画执行时间, 动画算法, 回调函数的对象.   
  6. //speed方法把animate函数的参数包装成一个对象方便以后调用,   
  7. //并且修正callback回调函数, 加上dequeue的功能.   
  8.       
  9.         if  ( jQuery.isEmptyObject( prop ) ) {  
  10.         //prop 是个空对象.不需要执行动画,直接调用回调函数.   
  11.             return   this .each( optall.complete );  
  12.         }  
  13.         return   this [ optall.queue ===  false  ?  "each"  :  "queue"  ](function() {  
  14.             //如果参数里指定了queue 为false, 单独执行这次动画,      
  15.          //而不是默认的加入队列.   
  16.             var opt = jQuery.extend({}, optall), p,  
  17.             //复制一下optall对象.   
  18.             isElement = this .nodeType ===  1 ,  
  19.             //是否是有效dom节点.   
  20.             hidden = isElement && jQuery(this ).is( ":hidden" ),  
  21.             //元素的可见性   
  22.             self = this ;  
  23.          //保存this的引用, 在下面的闭包中this指向会被改变   
  24.             for  ( p in prop ) {  
  25.             //遍历"left", "top"等需要执行动画的属性.   
  26.                 var name = jQuery.camelCase( p );  
  27.             //把margin-left之类的属性转换成marginLeft   
  28.                 if  ( p !== name ) {  
  29.                     prop[ name ] = prop[ p ];  
  30.                 //把值复制给camelCase转化后的属性   
  31.                     delete prop[ p ];  
  32.                 //删除已经无用的属性   
  33.                     p = name;  
  34.                 }  
  35.                 if  ( prop[p] ===  "hide"  && hidden || prop[p] ===  "show"  && !hidden ) {  
  36.                 //元素在hidden状态下再隐藏或者show状态下再显示   
  37.                     return  opt.complete.call( this );  
  38.                 //不需要任何操作, 直接调用回调函数.   
  39.                 }  
  40.   
  41.                 if  ( isElement && ( p ===  "height"  || p ===  "width"  ) ) {  
  42.                 //如果是改变元素的height或者width   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值