jQuery源码解析之$().animate()(上)
前言:
需要先看 jQuery源码解析之$
.queue()、$
.dequeue()和jQuery.Callbacks()
一、举例divA
的宽度先变成 500px,再变成 300px,最后变成 1000px:
<script src="jQuery.js"></script> <div id="A" style="width:100px;height:50px;background-color: deeppink">这是A</div> <script> let A = document.querySelector('#A'); //在异步调用中,进行同步调用 //动画是异步的 A.onclick = function() { //就是连续调用animation.add() $('#A').animate({ 'width': '500' }).animate({ 'width': '300' }).animate({ 'width': '1000' }); }; </script>
二、$
().animate()
作用:
通过 CSS 样式将元素从一个状态改变为另一个状态
源码:
//之前有说过: jQuery.fn.extend() 是$()的方法 jQuery.fn.extend( { //源码8062行 //{'width': '500'} animate: function( prop, speed, easing, callback ) { //是否是空对象,false var empty = jQuery.isEmptyObject( prop ), // optall={ // complete:function(){jQuery.dequeue()}, // old:false, // duration: 400, // easing: undefined, // queue:"fx", // } //undefined undefined undefined optall = jQuery.speed( speed, easing, callback ), doAnimation = function() { // Operate on a copy of prop so per-property easing won't be lost //Animation 方法执行单个动画的封装 //doAnimation的本质是执行Animation方法 //this:目标元素 //{'width': '500'} //optall={xxx} var anim = Animation( this, jQuery.extend( {}, prop ), optall ); // Empty animations, or finishing resolves immediately //finish是数据缓存的一个全局变量 //如果为true,立即终止动画 if ( empty || dataPriv.get( this, "finish" ) ) { anim.stop( true ); } }; //注意这个 //自身的.finish=自身 doAnimation.finish = doAnimation; return empty || optall.queue === false ? this.each( doAnimation ) : //一般走这里 //通过 queue 调度动画之间的衔接 //optall.queue:"fx" //doAnimation:function(){} this.queue( optall.queue, doAnimation ); }, })
解析:
(1)jQuery.speed()
作用:
初始化动画对象的属性
源码:
//源码8009行 //undefiend undefined undefined //作用是返回一个经过修改的opt对象 jQuery.speed = function( speed, easing, fn ) { // opt={ // complete:false, // duration: undefined, // easing: undefined, // } var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !isFunction( easing ) && easing }; // Go to the end state if fx are off /*这边是为opt.duration赋值的*/ //undefiend if ( jQuery.fx.off ) { opt.duration = 0; } else { if ( typeof opt.duration !== "number" ) { if ( opt.duration in jQuery.fx.speeds ) { opt.duration = jQuery.fx.speeds[ opt.duration ]; } else { //走这边 //_default=400 opt.duration = jQuery.fx.speeds._default; } } } /*为opt.queue赋值*/ // Normalize opt.queue - true/undefined/null -> "fx" //注意这个==,是模糊判断 if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing /*为opt.old赋值*/ opt.old = opt.complete; opt.complete = function() { if ( isFunction( opt.old ) ) { opt.old.call( this ); } //如果queue有值得话,就出队列 //因为queue默认为"fx",所以一般会触发dequeue操作 if ( opt.queue ) { //this指目标元素 //opt.queue jQuery.dequeue( this, opt.queue ); } }; //此时的opt为: // opt={ // complete:function(){jQuery.dequeue()}, // old:false, // duration: 400, // easing: undefined, // queue:"fx", // } return opt; };
解析:
通过传入的三个参数,对opt
对象进行处理,如果三个参数都为undefined
的话,opt
等于:
opt={ complete:function(){jQuery.dequeue()}, old:false, duration: 400, easing: undefined, queue:"fx", }
(2)animate
内部做了什么?
作用:animate
内部封装了一个doAnimation
触发器,触发器触发就会运行Animation
方法,animate
最后返回的是queue()
方法,注意queue()
方法的参数带有触发器doAnimation
(3)$().queue()
作用:
执行入队操作(jQuery.queue()
),如果是fx动画的话,同时执行出队操作(jQuery.dequeue()
)
源码
这个方法上篇文章已经分析过了,这里就简单分析下:
jQuery.fn.extend( { //optall.queue:"fx" //doAnimation:function(){} //fx动画的话,就执行dequeue(),也就是队首callback函数 queue: function( type, data ) { //返回的是数组[doAnimate,doAnimate,xxx] var queue = jQuery.queue( this, type, data ); //初始化hooks jQuery._queueHooks( this, type ); /*专门为fx动画做处理*/ //如果队首没有锁的话,直接运行队首元素 if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { jQuery.dequeue( this, type ); } },
解析:inprogress
是动画队列的锁,目的是保证上个动画执行结束后,再去执行下个动画
每入队一个doAnimate
函数,如果队首没有inprogress
锁的话,就会出队去运行一个doAnimate
函数
jQuery._queueHooks()
的意义在于添加一个empty.remove()
方法,用来清空队列queue
(4)jQuery.queue()
作用:
上篇文章也分析过了,就是将doAnimate
函数push
进queue
数组中
(5)jQuery.dequeue()
作用:
如果队首元素不是inprogress
,而是doAnimation
方法,则先将doAnimation
出队,再让inprogress
入队首,再运行doAnimation
方法;
如果队首元素是inprogress
的话,则移除锁
如果队列为空的话,则清空queue
,节省内存
源码:
//源码4624行 //目标元素,'type' dequeue: function( elem, type ) { //'type' type = type || "fx"; //get,获取目标元素的队列 var queue = jQuery.queue( elem, type ), //长度 startLength = queue.length, //去除对首元素,并返回该元素 fn = queue.shift(), //确保该队列有一个hooks hooks = jQuery._queueHooks( elem, type ), //next相当于dequeue的触发器 next = function() { jQuery.dequeue( elem, type ); }; // If the fx queue is dequeued, always remove the progress sentinel //如果fn='inprogress',说明是fx动画队列正在出队,就移除inprogress if ( fn === "inprogress" ) { fn = queue.shift(); startLength--; } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued //如果是fx动画队列的话,就添加inprogress标志,来防止自动出队执行 //意思应该是等上一个动画执行完毕后,再执行下一个动画 if ( type === "fx" ) { queue.unshift( "inprogress" ); } // Clear up the last queue stop function //删除hooks的stop属性方法 delete hooks.stop; //递归dequeue方法 fn.call( elem, next, hooks ); } console.log(startLength,'startLength4669') //如果队列是一个空数组,并且hooks存在的话,清除该队列 if ( !startLength && hooks ) { //进行队列清理 hooks.empty.fire(); console.log(hooks.empty.fire(),'bbbb4671') } },
解析:
循环同步运行多个doAnimation()
方法,直到队列为空
综上,除doAnimation
内的逻辑外,整个$().animate()
的流程图为: