jQuery源码学习12——动画加强
实例化方法queue和原生方法dequeue实现了队列的管理
当实现很复杂的动画时,队列管理显得很重要
举例来说
$("#div1").animate({ "width":400, "height":300 },300).animate({ "left":600 },300);
这个动画希望先让#div1的宽变到400,高变到300,然后再让向右移动600
需要注意的是在进行第一步操作的时候高一定会先到达目的值300,但是高到达目的值的时候宽不一定到了
而且希望宽变成400,高变成300完事了以后再向右移动,而不是变宽变高的同时还移动着
先看第一个animate的调用
在animate内部,首先还是调用了queue实例化方法,传给queue的函数将作为一个队列函数存到DOM元素的queue.fx属性中
第一次存进去的时候会顺便调用这个函数,调用的过程中给这个DOM元素绑定curAnim属性
curAnim属性的值即为传进来的prop,在此处就是
{ "width":400, "height":300 }
接下来prop里面有几项,就会创建几个jQuery.fx对象
在此会创建两个对象,暂且叫做widthfx对象和heightfx对象
var e = new jQuery.fx( this, jQuery.speed(speed,callback), p );
jQuery.fx的第一个参数是DOM对象,第二个参数是配置参数,第三个参数是要修改的样式的名称
其中第二个参数形如
{ "duration":300, "oldComplete":undefined, "complete":function(){ jQuery.dequeue(this,"fx"); } }
接下来执行实例化对象e的custom方法
z.custom = function(from,to){ z.startTime = (new Date()).getTime(); z.now = from; z.a(); z.timer = setInterval(function(){ z.step(from, to); }, 13); };
custom方法里面又开了一个定时器调用了step方法
但是第一次执行step也得等到13ms之后
而js不会等着,不会什么都不干就等着13ms之后执行step
定时器定义完毕之后custom方法执行完毕,继续执行animate里面的for in循环
创建heightfx对象,然后调用heightfx对象的custom方法开一个定时器
和widthfx对象一样,js也不会等着13ms之后执行step
定时器定义完毕之后custom方法执行完毕,此时animate里面的for in循环也循环完了
第一个animate就执行完了
继续执行第二个animate
和刚才的操作一样,也会调用queue方法,当执行完
this.queue[type].push( fn );
这一句时,通过分析我们可以知道这时this的queue.fx属性的值为
[ function(){ //此处的prop是 //{ // "width":400, // "height":300 //} this.curAnim = prop; for ( var p in prop ) { var e = new jQuery.fx( this, jQuery.speed(speed,callback), p ); if ( prop[p].constructor == Number ) e.custom( e.cur(), prop[p] ); else e[ prop[p] ]( prop ); } }, function(){ //此处的prop是 //{ // "left":600 //} this.curAnim = prop; for ( var p in prop ) { var e = new jQuery.fx( this, jQuery.speed(speed,callback), p ); if ( prop[p].constructor == Number ) e.custom( e.cur(), prop[p] ); else e[ prop[p] ]( prop ); } } ]
这其实就是this(#div1)上面fx类型的队列,其中第一个函数所代表的width和height的运动正在进行
再往下是实现队列很关键的一点:
if ( this.queue[type].length == 1 ) fn.apply(this);
这里很明显不符合if里面的条件,因此刚刚往this(#div1)的queue.fx属性中添加的队列函数并不执行
等什么时候执行呢?会等第一个队列函数的width和height完事了之后才执行
实现这个功能的原理就是函数回调,接下来一步一步分析
这个queue方法接下来就执行完了,同时这第二个animate也就执行完了
一切看起来似乎都完事了
但是不要忘了刚才我们开了两个定时器
从绑定时器那个时候开始过13ms之后,这两个定时器分别会触发
所以两个animate执行完了之后真正的"动"画才刚开始
需要注意的是两个定时器是分属两个不同的fx实例化对象的:
z.custom = function(from,to){ z.startTime = (new Date()).getTime(); z.now = from; z.a(); z.timer= setInterval(function(){ z.step(from, to); }, 13); };
因此看起来好像是两个定时器同时走,同时执行step函数,即#div1的width和height好像在同时改变
每执行一次step函数,都会用时间戳判断是否到了目的值
没有到目的值的话就每次递增或递减
关键看到目的值的情况,准确的说是超过目的值的情况
以heightfx对象的z.timer定时器为例
首先,既然超了目的值,自然会关掉定时器
并把定时器属性z.timer置为null,再将准确的目的值赋给对应的样式
再将this(#div1)的curAnim属性里面的height子属性的值置为true
此时需要注意,当height超过目的值的时候width还不一定到目的值
因为height到300就可以了,而width得到400
所以heightfx的定时器关掉了,而widthfx的定时器还在跑
而且在接下来的for in循环中由于z.el.curAnim.height!==true的条件是成立的
所以局部变量done变成了false
于是接下来的两个if都不会执行
分析到这里再往下也就很顺了
当width也到目的值400的时候再次走这里的for in循环
局部变量的值就是true了
接下来的两个if就要执行了
第一个没什么好说的,关键看第二个if
if( done && z.o.complete && z.o.complete.constructor == Function ) z.o.complete.apply( z.el );
z.o.complete里面有一个操作是核心
jQuery.dequeue(this, "fx");
我们直接进到这个方法内部
dequeue: function(elem,type){ type = type || "fx"; if ( elem.queue && elem.queue[type] ) { elem.queue[type].shift(); var f = elem.queue[type][0]; if ( f ) f.apply( elem ); } },
前面我们分析到#div1上的queue.fx属性是一个数组,存放的是函数队列
既然都走到这一步了,那么队列里面的第一个函数就执行完了
queue.fx队列就会把第一个函数给shift掉
再把这个队列里面现在的第一个队列函数取出来执行
以此类推
其实jQuery这样做达到了一个很好的效果,那就是避免了深层嵌套
深层嵌套的工作在jQuery内部全部完成