首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > Web前端 >

jquery1.43源码分析之卡通部分

2012-11-14 
jquery1.43源码分析之动画部分js实现动画的原理跟动画片的制作一样.动画片是把一些差距不大的原画以一定帧

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, 0, 1, this.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, 0, 1, this.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 onclick="stop()">停止</button> 
154. <button onclick="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 onclick="stop()">停止</button>
<button onclick="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 
  43.                     opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; 
  44.                     if ( jQuery.css( this, "display" ) === "inline" && 
  45.                             jQuery.css( this, "float" ) === "none" ) { 
  46.                         if ( !jQuery.support.inlineBlockNeedsLayout ) { 
  47.      //对于不支持inline-block的浏览器,可以加上zoom:1 来hack  //http://www.planabc.net/2007/03/11/display_inline-block/  
  48.         this.style.display = "inline-block";   
  49.                         } else { 
  50.                 var display = defaultDisplay(this.nodeName); 
  51.                             if ( display === "inline" ) { 
  52.                                 this.style.display = "inline-block"; 
  53.                             } else { 
  54.                                 this.style.display = "inline"; 
  55.                                 this.style.zoom = 1; 
  56.                             } 
  57.                         } 
  58.                     } 
  59.                 } 
  60.                 if ( jQuery.isArray( prop[p] ) ) { 
  61.     (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; 
  62.     // 可以给某个属性单独定制动画算法. 
  63. //例如$("#div1").animate({"left":["+=100px", "swing"], 1000},  
  64.                     prop[p] = prop[p][0]; 
  65.               //修正prop[p] 
  66.                 } 
  67.             } 
  68.  
  69.             if ( opt.overflow != null ) { 
  70.                 this.style.overflow = "hidden"; 
  71.                 //动画改变元素大小时, overflow设置为hidden, 避免滚动条也跟着不停改变 
  72.             } 
  73.             opt.curAnim = jQuery.extend({}, prop); 
  74. //又复制一次prop, opt.curAnim用来记录某个元素中已经完成动画的属性, 比如left和marginLeft的动画并不是同一时间完成的,而只有全部属性的动画都完成之后, 才能触发这个元素的这次动画的callback函数.            
  75. jQuery.each( prop, function( name, val ) { 
  76.              //进入重点, 遍历属性. 
  77.                 var e = new jQuery.fx( self, opt, name ); 
  78.              //fx是个辅助工具类. 为每个属性的动画操作都生成一个fx对象. 
  79.              //得到一个具有elem(被操作对象), options(包括speed, callback等属性), prop(elem待变化的属性, 比如left, top),  
  80. //orig为{}, 用来保存属性的原始值 
  81.                 if ( rfxtypes.test(val) ) { 
  82.                     e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); 
  83.     //动画方式进行show. hide和toggle.    
  84. //比如$("#div1").animate({"width":"hide"}, 1000)               
  85. //这里进入是fx.prototype.show,fx.prototype.hide方法 
  86.  //并不是jQuery.fn.show, jQueyr.fn.hide 
  87.            } else { 
  88.                     var parts = rfxnum.exec(val), 
  89.                         start = e.cur(true) || 0;   
  90.                         //修正开始的位置, 如果是一个太大的负数,把它                           
  91.                   //修正为0 
  92.                     if ( parts ) { 
  93.                         var end = parseFloat( parts[2] ), 
  94.                             //结束的位置 
  95.                             unit = parts[3] || "px";  //修正单位 
  96.                         // We need to compute starting value 
  97.                         if ( unit !== "px" ) { 
  98.                             self.style[ name ] = (end || 1) + unit; 
  99.                             start = ((end || 1) / e.cur(true)) * start; 
100.                             self.style[ name ] = start + unit; 
101.                         } 
102.                         //修正开始的位置,unit可能为%. 
103.      
104.                         if ( parts[1] ) { 
105.                             end = ((parts[1] === "-=" ? -1 : 1) * end) + start; 
106.                         //做相对变化时, 计算结束的位置, 比如 
107.           //{"left":"+=100px"}, 表示元素右移100个像素         
108.                         } 
109.                         e.custom( start, end, unit ); 
110.                //调用fx类的prototype方法custom, 真正开始动画 
111.                     } else { 
112.                         e.custom( start, val, "" ); 
113.                     } 
114.                 } 
115.             }); 
116.  
117.             // For JS strict compliance 
118.             return true; 
119.         }); 
120.     } 

animate: function( prop, speed, easing, callback ) {
//prop是{"left":"100px", "paddingLeft": "show"}这种形式,可以由用户自己传进来, 在show,   hide等方法里, 是由genfx
//函数转化而来.
var optall = jQuery.speed(speed, easing, callback);
//optall是一个包含了修正后的动画执行时间, 动画算法, 回调函数的对象.
//speed方法把animate函数的参数包装成一个对象方便以后调用,
//并且修正callback回调函数, 加上dequeue的功能.

if ( jQuery.isEmptyObject( prop ) ) {
//prop 是个空对象.不需要执行动画,直接调用回调函数.
return this.each( optall.complete );
}
return this[ optall.queue === false ? "each" : "queue" ](function() {
//如果参数里指定了queue 为false, 单独执行这次动画,  
         //而不是默认的加入队列.
var opt = jQuery.extend({}, optall), p,
//复制一下optall对象.
isElement = this.nodeType === 1,
//是否是有效dom节点.
hidden = isElement && jQuery(this).is(":hidden"),
//元素的可见性
self = this;
     //保存this的引用, 在下面的闭包中this指向会被改变
for ( p in prop ) {
//遍历"left", "top"等需要执行动画的属性.
var name = jQuery.camelCase( p );
//把margin-left之类的属性转换成marginLeft
if ( p !== name ) {
prop[ name ] = prop[ p ];
//把值复制给camelCase转化后的属性
delete prop[ p ];
//删除已经无用的属性
p = name;
}
if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
//元素在hidden状态下再隐藏或者show状态下再显示
return opt.complete.call(this);
//不需要任何操作, 直接调用回调函数.
}

if ( isElement && ( p === "height" || p === "width" ) ) {
//如果是改变元素的height或者width
opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
if ( jQuery.css( this, "display" ) === "inline" &&
jQuery.css( this, "float" ) === "none" ) {
if ( !jQuery.support.inlineBlockNeedsLayout ) {
     //对于不支持inline-block的浏览器,可以加上zoom:1 来hack  //http://www.planabc.net/2007/03/11/display_inline-block/
this.style.display = "inline-block"; 
} else {
var display = defaultDisplay(this.nodeName);
if ( display === "inline" ) {
this.style.display = "inline-block";
} else {
this.style.display = "inline";
this.style.zoom = 1;
}
}
}
}
if ( jQuery.isArray( prop[p] ) ) {
(opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
// 可以给某个属性单独定制动画算法.
//例如$("#div1").animate({"left":["+=100px", "swing"], 1000},
prop[p] = prop[p][0];
              //修正prop[p]
}
}

if ( opt.overflow != null ) {
this.style.overflow = "hidden";
//动画改变元素大小时, overflow设置为hidden, 避免滚动条也跟着不停改变
}
opt.curAnim = jQuery.extend({}, prop);
//又复制一次prop, opt.curAnim用来记录某个元素中已经完成动画的属性, 比如left和marginLeft的动画并不是同一时间完成的,而只有全部属性的动画都完成之后, 才能触发这个元素的这次动画的callback函数.
jQuery.each( prop, function( name, val ) {
//进入重点, 遍历属性.
var e = new jQuery.fx( self, opt, name );
//fx是个辅助工具类. 为每个属性的动画操作都生成一个fx对象.
//得到一个具有elem(被操作对象), options(包括speed, callback等属性), prop(elem待变化的属性, 比如left, top),
//orig为{}, 用来保存属性的原始值
if ( rfxtypes.test(val) ) {
e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
//动画方式进行show. hide和toggle.  
//比如$("#div1").animate({"width":"hide"}, 1000)
//这里进入是fx.prototype.show,fx.prototype.hide方法
//并不是jQuery.fn.show, jQueyr.fn.hide
           } else {
var parts = rfxnum.exec(val),
start = e.cur(true) || 0; 
//修正开始的位置, 如果是一个太大的负数,把它                         
                  //修正为0
if ( parts ) {
var end = parseFloat( parts[2] ),
//结束的位置
unit = parts[3] || "px";  //修正单位
// We need to compute starting value
if ( unit !== "px" ) {
self.style[ name ] = (end || 1) + unit;
start = ((end || 1) / e.cur(true)) * start;
self.style[ name ] = start + unit;
}
//修正开始的位置,unit可能为%.

if ( parts[1] ) {
end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
//做相对变化时, 计算结束的位置, 比如
          //{"left":"+=100px"}, 表示元素右移100个像素       
}
e.custom( start, end, unit );
   //调用fx类的prototype方法custom, 真正开始动画
} else {
e.custom( start, val, "" );
}
}
});

// For JS strict compliance
return true;
});
}


看看animate里的speed函数. 前面说过speed函数是把animate的参数都包装为一个对象. 然后把dequeue加入回调函数.如果animate的第二个参数是object类型,直接复制一次就可以了, 如果不是, 要自己手动包装.
Java代码

   1.      
   2.   speed: function( speed, easing, fn ) { 
   3.         var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { 
   4.             complete: fn || !fn && easing || 
   5.             jQuery.isFunction( speed ) && speed, 
   6.             duration: speed, 
   7.             easing: fn && easing || easing && !jQuery.isFunction(easing) && easing 
   8.         }; 
   9.         opt.duration = jQuery.fx.off ? 0 : typeof opt.duration ===  
  10. "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; 
  11.           //jQuery.fx.off为true时,禁止执行动画,此时任何animate       
  12.         //的speed都为0 
  13.         //如果speed为"fast","slow"等, 从jQuery.fx.speeds里取 
  14.         //值, 分别为600,200. 
  15.         opt.old = opt.complete; 
  16.         opt.complete = function() { 
  17.             //重写回调函数, 把dequeue操作加入回调函数. 
  18.             //让动画按顺序执行 
  19.             if ( opt.queue !== false ) { 
  20.           //前面的animate方法里已经说过, queue参数为false时, 
  21.         // 是直接执行此次动画, 不必加入队列. 
  22.         jQuery(this).dequeue(); 
  23.             } 
  24.             if ( jQuery.isFunction( opt.old ) ) { 
  25.                 opt.old.call( this ); 
  26.             } 
  27.         }; 
  28.         return opt; 
  29.          //返回包装后的对象 
  30.     } 


  speed: function( speed, easing, fn ) {
var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
complete: fn || !fn && easing ||
jQuery.isFunction( speed ) && speed,
duration: speed,
easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
};
opt.duration = jQuery.fx.off ? 0 : typeof opt.duration ===
"number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
  //jQuery.fx.off为true时,禁止执行动画,此时任何animate     
        //的speed都为0
        //如果speed为"fast","slow"等, 从jQuery.fx.speeds里取
        //值, 分别为600,200.
opt.old = opt.complete;
opt.complete = function() {
//重写回调函数, 把dequeue操作加入回调函数.
//让动画按顺序执行
if ( opt.queue !== false ) {
  //前面的animate方法里已经说过, queue参数为false时,
        // 是直接执行此次动画, 不必加入队列.
jQuery(this).dequeue();
}
if ( jQuery.isFunction( opt.old ) ) {
opt.old.call( this );
}
};
return opt;
         //返回包装后的对象
}


jQuery.fx
再看看fx类的实现. 它的构造方法接受3个从animate函数里传来的参数. 每个实例在构造函数里初始化的时候都会被加上3个属性. 分别是
options({“duration”:1000,“easing”:“swing”,“callback”:fn})
elem (元素本身)
prop ({"left": 500, "width": 500})
这几种形式
Java代码

   1. fx: function( elem, options, prop ) { 
   2.         this.options = options; 
   3.         this.elem = elem; 
   4.         this.prop = prop; 
   5.  
   6.         if ( !options.orig ) { 
   7.             options.orig = {}; 
   8.         } 
   9.     } 
  10. }) 

fx: function( elem, options, prop ) {
this.options = options;
this.elem = elem;
this.prop = prop;

if ( !options.orig ) {
options.orig = {};
}
}
})


另外, 还会给每个对象实例绑定orig属性, 默认是一个空对象{}.用来储存元素的属性原始值, 有了这个值, 可以让动画进行回退.

现在终于到了custom函数. 前面的这么多代码都只是铺垫, 这个函数里才开始真正的进入执行动画. 它主要作用是创建定时器.
Java代码

   1. // Start an animation from one number to another 
   2.     custom: function( from, to, unit ) { 
   3.         this.startTime = jQuery.now(); 
   4.         this.start = from; 
   5.         this.end = to; 
   6.         this.unit = unit || this.unit || "px"; 
   7.         this.now = this.start; 
   8.         this.pos = this.state = 0; 
   9.  
  10.         var self = this, fx = jQuery.fx; 
  11.         function t( gotoEnd ) { 
  12.             return self.step(gotoEnd); 
  13.         } 
  14.      //一个包裹动画具体执行函数的闭包, 之所以要用闭包包裹起来, 
  15.      //是因为执行动画的动作并不一定是发生在当前. 
  16.         t.elem = this.elem;   
  17.         //保存一个元素的引用. 删除和判断is:animated的时候用到,  
  18.      //把timer和元素联系起来 
  19.         if ( t() && jQuery.timers.push(t) && !timerId ) {---(1) 
  20.             timerId = setInterval(fx.tick, fx.interval); 
  21.         } 
  22.     } 
  23.   

// Start an animation from one number to another
custom: function( from, to, unit ) {
this.startTime = jQuery.now();
this.start = from;
this.end = to;
this.unit = unit || this.unit || "px";
this.now = this.start;
this.pos = this.state = 0;

var self = this, fx = jQuery.fx;
function t( gotoEnd ) {
return self.step(gotoEnd);
}
     //一个包裹动画具体执行函数的闭包, 之所以要用闭包包裹起来,
     //是因为执行动画的动作并不一定是发生在当前.
t.elem = this.elem; 
//保存一个元素的引用. 删除和判断is:animated的时候用到,
     //把timer和元素联系起来
if ( t() && jQuery.timers.push(t) && !timerId ) {---(1)
timerId = setInterval(fx.tick, fx.interval);
}
}



1处这个if语句里包含了很多内容
首先t()进入.step函数, step函数可以看成动画的某一帧.
而custom函数至多只负责执行动画的第一帧(其它帧都是在全局定时器里执行的),然后判断t()的返回值, 即self.step(gotoEnd)的返回值, 如果为false, 表示这个fx动画已经执行完.所以当它为true,也就是还需要继续执行动画的情况下,要把这个包含self.step(gotoEnd)语句的闭包, 存入全局的timers数组. 以便在定时器里循环执行.我在前面的模拟fx类的实现中, 选择的存入fx对象,把判断fx对象执行完毕的操作放在fx.step里面. 效果是一样.不过觉得存fx对象的话, 没有这么难理解.而timerId就是那个全局计时器,用来不停的执行timer数组里残余的动画.只有所有的动画都执行完毕后, 才会关闭timerId定时器.当然如果t()返回false和timerId定时器已经存在的情况下,都不需要再启动定时器.

定时器里的调用的函数是fx.tick. 看看fx.tick的实现.
Java代码

   1. tick: function() { 
   2.         var timers = jQuery.timers; 
   3.         //timer里面包括所有当前所有在执行动画的函数. 
   4.         for ( var i = 0; i < timers.length; i++ ) { 
   5.             if ( !timers[i]() ) { 
   6.         //timers[i]()就是上面的t(),也就是fx.step(gotoEnd); 
   7.     //动画如果完成了,fx.step(gotoEnd)是返回false的. 所以这里  
   8.     //是当某个动画完成之后, 就在全局的timer中删除它 
   9.                 timers.splice(i--, 1); 
  10.             } 
  11.         } 
  12.  
  13.         if ( !timers.length ) { 
  14.         //当全部动画完成之后, 清空定时器 
  15.             jQuery.fx.stop(); 
  16.         } 
  17.     } 

tick: function() {
var timers = jQuery.timers;
//timer里面包括所有当前所有在执行动画的函数.
for ( var i = 0; i < timers.length; i++ ) {
if ( !timers[i]() ) {
//timers[i]()就是上面的t(),也就是fx.step(gotoEnd);
//动画如果完成了,fx.step(gotoEnd)是返回false的. 所以这里
//是当某个动画完成之后, 就在全局的timer中删除它
timers.splice(i--, 1);
}
}

if ( !timers.length ) {
//当全部动画完成之后, 清空定时器
jQuery.fx.stop();
}
}


jQuery.fx.stop方法很简单
Java代码

   1. stop: function() { 
   2.         clearInterval( timerId ); 
   3.         timerId = null; 
   4.     } 

stop: function() {
clearInterval( timerId );
timerId = null;
}


再看下fx的一些原型方法,最重要的就是fx.prototype.step.
认真分析下这个函数的实现.
step函数用来执行动画的某一帧, 它有一个可选的参数gotoEnd.
当gotoEnd为true时,直接让元素转到动画完成的状态.主要用在$('div').stop的时候.
step函数每次被调用的时候都会根据gotoEnd参数和当前时间跟开始时间加上持续的时间的比值,来判断动画是否结束.如果结束了就返回false. 在定时器下一次的循环里继续执行.
否则, 会算出元素在这一帧上面的状态, 调用fx.prototype.update来在页面上绘制这一帧.并且返回true,在定时器下一次开始循环timer数组的时候, 把包含这个step函数的闭包给删除掉.
Java代码

   1. // Each step of an animation 
   2.     step: function( gotoEnd ) { 
   3.         var t = jQuery.now(), done = true; 
   4.  
   5.         if ( gotoEnd || t >= this.options.duration + this.startTime ) { 
   6.             //如果gotoEnd为true或者已到了动画完成的时间. 
   7.             this.now = this.end; 
   8.             this.pos = this.state = 1; 
   9.         //动画的确切执行时间总是一个指定的number的倍数, 很难刚好等于要求的动画持续时间,所以一般可能会有小小的误差, 修正下动画最后的属性值. 
  10.             this.update(); 
  11.             //进入uptate,重新绘制元素的最后状态. 
  12.             this.options.curAnim[ this.prop ] = true; 
  13.             //某个属性的动画已经完成. 
  14.             for ( var i in this.options.curAnim ) { 
  15.                 if ( this.options.curAnim[i] !== true ) { 
  16.             //如果有一个属性的动画没有完成,都不认为该次animate操  
  17.          //作完成, 其实这里完全可以事先用一个number计算出有多   
  18.        //少个需要执行动画的属性.每次step完成就减1, 直到等于0. 
  19.         //效率明显会高一点. 
  20.                 done = false; 
  21.                 } 
  22.             } 
  23.  
  24.             if ( done ) { 
  25.                 // 恢复元素的overflow 
  26.                 if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { 
  27.                     var elem = this.elem, options = this.options; 
  28.                     jQuery.each( [ "", "X", "Y" ], function (index, value) { 
  29.                         elem.style[ "overflow" + value ] = options.overflow[index]; 
  30.                     } ); 
  31.                 } 
  32.                 // Hide the element if the "hide" operation was done 
  33.                 if ( this.options.hide ) { 
  34.                     jQuery(this.elem).hide(); 
  35.                 //如果要求hide, 动画完成后,通过设置display真正隐 
  36.             //藏元素.动画的最后只是设置width,height等为0. 
  37.                 } 
  38.                 if ( this.options.hide || this.options.show ) { 
  39.                     for ( var p in this.options.curAnim ) { 
  40.                         jQuery.style( this.elem, p, this.options.orig[p] ); 
  41.                     } 
  42.                 } 
  43.                 //hide,show操作完成后,修正属性值. 通过hide/show动  
  44.     //画开始前记录的原始属性值 
  45.                 //执行回调函数 
  46.                 this.options.complete.call( this.elem ); 
  47.             } 
  48.             return false; 
  49.     //动画完成, 返回false 
  50.         } else { 
  51.             //动画还没执行完. 
  52.             var n = t - this.startTime; 
  53.             //动画执行完还需要的时间 
  54.             this.state = n / this.options.duration; 
  55.             //还需要时间的比例 
  56.             // Perform the easing function, defaults to swing 
  57.             var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; 
  58.             //某个属性如果指定了额外的动画算法. 
  59.             var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); 
  60.             //所有属性公用的动画算法, 如果没有指定. 默认为"swing"或者"linear" 
  61.             this.pos=jQuery.easing[specialEasing|| defaultEasing](this.state, n, 0, 1, this.options.duration); 
  62.             //通过动画算法, 计算属性现在的位置. 
  63.             this.now = this.start + ((this.end - this.start) * this.pos); 
  64.             this.update(); //重新绘制元素在这一帧的状态 
  65.         } 
  66.         return true;   //此fx动画还未结束, 返回true. 
  67.     } 
  68. }; 

// Each step of an animation
step: function( gotoEnd ) {
var t = jQuery.now(), done = true;

if ( gotoEnd || t >= this.options.duration + this.startTime ) {
//如果gotoEnd为true或者已到了动画完成的时间.
this.now = this.end;
this.pos = this.state = 1;
        //动画的确切执行时间总是一个指定的number的倍数, 很难刚好等于要求的动画持续时间,所以一般可能会有小小的误差, 修正下动画最后的属性值.
this.update();
//进入uptate,重新绘制元素的最后状态.
this.options.curAnim[ this.prop ] = true;
//某个属性的动画已经完成.
for ( var i in this.options.curAnim ) {
if ( this.options.curAnim[i] !== true ) {
//如果有一个属性的动画没有完成,都不认为该次animate操
         //作完成, 其实这里完全可以事先用一个number计算出有多 
       //少个需要执行动画的属性.每次step完成就减1, 直到等于0.
        //效率明显会高一点.
       done = false;
}
}

if ( done ) {
// 恢复元素的overflow
if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
var elem = this.elem, options = this.options;
jQuery.each( [ "", "X", "Y" ], function (index, value) {
elem.style[ "overflow" + value ] = options.overflow[index];
} );
}
// Hide the element if the "hide" operation was done
if ( this.options.hide ) {
jQuery(this.elem).hide();
//如果要求hide, 动画完成后,通过设置display真正隐
            //藏元素.动画的最后只是设置width,height等为0.
}
if ( this.options.hide || this.options.show ) {
for ( var p in this.options.curAnim ) {
jQuery.style( this.elem, p, this.options.orig[p] );
}
}
//hide,show操作完成后,修正属性值. 通过hide/show动
//画开始前记录的原始属性值
//执行回调函数
this.options.complete.call( this.elem );
}
return false;
//动画完成, 返回false
} else {
//动画还没执行完.
var n = t - this.startTime;
//动画执行完还需要的时间
this.state = n / this.options.duration;
//还需要时间的比例
// Perform the easing function, defaults to swing
var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
//某个属性如果指定了额外的动画算法.
var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
//所有属性公用的动画算法, 如果没有指定. 默认为"swing"或者"linear"
this.pos=jQuery.easing[specialEasing|| defaultEasing](this.state, n, 0, 1, this.options.duration);
//通过动画算法, 计算属性现在的位置.
this.now = this.start + ((this.end - this.start) * this.pos);
this.update(); //重新绘制元素在这一帧的状态
}
return true;   //此fx动画还未结束, 返回true.
}
};


jQuery.fx.update
Java代码

   1. update: function() { 
   2.         if ( this.options.step ) { ------------------(1) 
   3.             this.options.step.call( this.elem, this.now, this ); 
   4.         } 
   5.     (jQuery.fx.step[this.prop]||jQuery.fx.step._default)( this ) 
   6.    ------------------(2) 
   7.     } 

update: function() {
if ( this.options.step ) { ------------------(1)
this.options.step.call( this.elem, this.now, this );
}
(jQuery.fx.step[this.prop]||jQuery.fx.step._default)( this )
   ------------------(2)
}


从update方法的(1)处可以看到, 当animate的第二个object类型参数有step 属性, 并且值是一个函数的话,在每一帧动画结束后, 都会执行这个callback函数.
(2)处绘制元素是调用的jQuery.fx.step, 不是刚才一直在讨论的fx.prototype.step. 看看jQuery.fx.step的代码.
Java代码

   1. step: { 
   2.         opacity: function( fx ) { 
   3.             jQuery.style( fx.elem, "opacity", fx.now ); 
   4.         //  opacity的操作不一样.IE要用filter:alpha .拿出来单独处理. 
   5.     }, 
   6.         _default: function( fx ) { 
   7.             if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { 
   8.                 fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; 
   9.             } else { 
  10.                 fx.elem[ fx.prop ] = fx.now; 
  11.             } 
  12.         } 
  13.     } 

step: {
opacity: function( fx ) {
jQuery.style( fx.elem, "opacity", fx.now );
//opacity的操作不一样.IE要用filter:alpha .拿出来单独处理.
},
_default: function( fx ) {
if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
} else {
fx.elem[ fx.prop ] = fx.now;
}
}
}



-----------------------分割线-------------------------------
到这里, fx类的中心实现就基本差不多了. 最后看看另外一些用到过的方法.

jQuery.fn.stop
这个方法让元素停止动画, 可以传入2个boolean类型的参数, clearQueue和gotoEnd. 作用分别是在stop的同时, 清空元素队列里的动画, 和让元素立即转换到动画结束后应该处在的状态(默认是动画执行一半的时候stop的话, 元素会停留在那个状态).
Java代码

   1. stop: function( clearQueue, gotoEnd ) { 
   2.         var timers = jQuery.timers; 
   3.         if ( clearQueue ) { 
   4.             this.queue([]); 
   5.        //清空元素的队列 
   6.         } 
   7.         this.each(function() { 
   8.             for ( var i = timers.length - 1; i >= 0; i-- ) { 
   9.                 //循环全局timers 
  10.             if ( timers[i].elem === this ) { 
  11.                     //找到timers中的跟这个元素对应的fx闭包, 
  12.                //可能有N个. 
  13.               if (gotoEnd) { 
  14.                         timers[i](true); 
  15.              //调用一次fx.step(true),转到动画执完成的状态 
  16.                     } 
  17.                     timers.splice(i, 1); 
  18.              //timers数组里删掉这个fx闭包 
  19.                 } 
  20.             } 
  21.         }); 
  22.         if ( !gotoEnd ) { 
  23.             this.dequeue(); 
  24.         } 
  25.         return this; 
  26.     } 
  27. }) 

stop: function( clearQueue, gotoEnd ) {
var timers = jQuery.timers;
if ( clearQueue ) {
this.queue([]);
       //清空元素的队列
}
this.each(function() {
for ( var i = timers.length - 1; i >= 0; i-- ) {
//循环全局timers
            if ( timers[i].elem === this ) {
//找到timers中的跟这个元素对应的fx闭包,
               //可能有N个.
              if (gotoEnd) {
timers[i](true);
             //调用一次fx.step(true),转到动画执完成的状态
}
timers.splice(i, 1);
             //timers数组里删掉这个fx闭包
}
}
});
if ( !gotoEnd ) {
this.dequeue();
}
return this;
}
})


fx.prototype.show和fx.prototype.hide
Java代码

   1. show: function() { 
   2.          //Remember where we started, so that we can go back to it later 
   3.         this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); 
   4.         this.options.show = true; 
   5.  
   6.         this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); 
   7. //把width和height设置为一个很小的值, 防止屏幕闪烁. 
   8.         // Start by showing the element 
   9.         jQuery( this.elem ).show(); 
  10.     }, 
  11.  
  12.     // Simple 'hide' function 
  13.     hide: function() { 
  14.         // Remember where we started, so that we can go back to it later 
  15.         this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); 
  16.         this.options.hide = true; 
  17.  
  18.         // Begin the animation 
  19.         this.custom(this.cur(), 0); 
  20.     } 

show: function() {
//Remember where we started, so that we can go back to it later
this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
this.options.show = true;

this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
//把width和height设置为一个很小的值, 防止屏幕闪烁.
// Start by showing the element
jQuery( this.elem ).show();
},

// Simple 'hide' function
hide: function() {
// Remember where we started, so that we can go back to it later
this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
this.options.hide = true;

// Begin the animation
this.custom(this.cur(), 0);
}


这2个方法是用于
$("#div1").animate({"width":"hide"},{duration:1000}).animate({"width":"show"}, 1000)这种情况.
当元素hide之前, 需要记录一下原来的width, 以便在接下来的show操作的时候, 回到原来的width.


然后是一些常用快捷方法.
Java代码

   1. jQuery.each({ 
   2.     slideDown: genFx("show", 1), 
   3.     slideUp: genFx("hide", 1), 
   4.     slideToggle: genFx("toggle", 1), 
   5.     fadeIn: { opacity: "show" }, 
   6.     fadeOut: { opacity: "hide" } 
   7. }, function( name, props ) { 
   8.     jQuery.fn[ name ] = function( speed, easing, callback ) { 
   9.         return this.animate( props, speed, easing, callback ); 
  10.     }; 
  11. }) 

jQuery.each({
slideDown: genFx("show", 1),
slideUp: genFx("hide", 1),
slideToggle: genFx("toggle", 1),
fadeIn: { opacity: "show" },
fadeOut: { opacity: "hide" }
}, function( name, props ) {
jQuery.fn[ name ] = function( speed, easing, callback ) {
return this.animate( props, speed, easing, callback );
};
})


可以看到, 都是调用的animate函数.
Java代码

   1. genFx 
   2.     fxAttrs = [ 
   3.         // height animations 
   4.         [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], 
   5.         // width animations 
   6.         [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], 
   7.         // opacity animations 
   8.         [ "opacity" ] 
   9.     ] 
  10.  
  11. function genFx( type, num ) { 
  12.     var obj = {}; 
  13.     jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { 
  14.         obj[ this ] = type; 
  15.     }); 
  16.  
  17.     return obj; 
  18. } 

genFx
fxAttrs = [
// height animations
[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
// width animations
[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
// opacity animations
[ "opacity" ]
]

function genFx( type, num ) {
var obj = {};
jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
obj[ this ] = type;
});

return obj;
}


这个方法就是取得进行一些特殊的动画操作时, 元素需要改变的属性.返回的是一个{key: value}类型的对象.

热点排行