一.前言
动画在jQuery里属于重要的模块,库中很多模块都为它服务,queue队列模块,callbacks模块,缓存模块,下面是我自己看源码做笔记所写下来的,话不多说,一下是我自己所记录的,大部分代码做了笔记 。
我将整个动画分为2个部分,今天我来分析下动画变化之前,做了些什么,怎么实现动画的链式,queue队列到底做了什么
下次会讲动画真正实现的源码,不过今天的这个部分,我个人觉得比较有意义,也是动画的基础框架,一个简单的队列算法,却经过他人的技巧和经验,达到了自己想要的结果,这就是玩转算法吧!
html
#aaron{
display:block;
width:100px;
height:30px;
background:#eee;
border:10px solid #ccc;
padding:30px;
;
}
<div id="aaron">
<div id="div">
<p id="p">
<a id='a' href="#">Aron test</a>
</p>
</div>
</div>
// 动画测试
$("#aaron").animate({height:"300px",width:"440px"},1000,"swing",function(){
console.log("Animation Show.")
})/*.animate({height:"30px",width:"110px"},1000,"linear",function(){
console.log("Animation Show.")
})*/;
如果不明白动画API的自己可以去看手册,我就精简来讲下
animate函数
animate: function( prop, speed, easing, callback ) {
// Object {height: "hide", paddingTop: "hide", marginTop: "hide", paddingRight: "hide", marginRight: "hide"…} "slow" fn undefined
var empty = jQuery.isEmptyObject( prop ), // false
// 组装参数
optall = jQuery.speed( speed, easing, callback ); // Object {complete: function, duration: 600, easing: undefined, queue: "fx", old: function}
// 每次动画函数都是把这个函数推入到queue中,只不过是prop,optall参数不同,产生不同的动画效果,其实还是一个一个动画执行,主不过可以链式写法
var doAnimation = function() {
// arguments [function, Object] 上下文为elem
//fn.call(next, hooks);
// Operate on a copy of prop so per-property easing won't be lost
// div#aaron Object object
// 这里执行动画的函数 elem,prop,optall
var anim = Animation( this, jQuery.extend( {}, prop ), optall );
// Empty animations, or finishing resolves immediately
if ( empty || jQuery._data( this, "finish" ) ) {
// 这段代码,好像没有执行
anim.stop( true );
}
};
doAnimation.finish = doAnimation;
return empty || optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation ); // this.queue('fx',doAnimation) jQuery.fn.queue
},
(1)optall = jQuery.speed( speed, easing, callback );
这句代码主要是把你传进来的动画参数,合并组装成一个对象
<pre name="code" class="javascript">jQuery.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
};
// 这里讲duration从slow转换成600 jQuery.fx.speeds = Object {slow: 600, fast: 200, _default: 400}
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;
// 默认动画队列名
if ( opt.queue == null || opt.queue === true ) {
opt.queue = "fx";
}
// Queueing
opt.old = opt.complete;
// luolaifa 第八个被推入的函数
opt.complete = function() {
if ( jQuery.isFunction( opt.old ) ) {
// this = div#aaron
opt.old.call( this );
}
// 出列执行下一个动画
if ( opt.queue ) {
jQuery.dequeue( this, opt.queue );
}
};
return opt;
};
根据传进来的参数,先初始化一些基本的属性,complete这个是将来当本动画完成所有回调的函数(其中主要是调用dequeue进行下一个动画)
duration动画时间,系统默认有3个速度值jQuery.fx.speeds = Object {slow: 600, fast: 200, _default: 400},如果没有穿对应的字符串,则是以你传进来的数值为动画时间(但是必须为数字)
easing是一个振幅参数,相当于数学当中的加速减速迅速的作用,默认为swing,还有一个linarn线性就是匀速
上面还给动画一个名称,opt.queue = fx ,如果没有传名称,则默认为fx
最后opt.old = opt.complete,就是用户自己传进来的回调函数,在complete中调用
最后返回一个组装好的参数对象
(2)doAnimation这个函数是传进queue队列的动画函数,是开始执行本次动画的入口
(3)this.queue( optall.queue, doAnimation );
//
this.queue('fx',doAnimation) jQuery.fn.queue
这里就是我们jQuery中的队列基础模块,这句是将动画函数进行插入队列尾部,并调用静态方法jQuery.dequeue出列执行
// 提供给用户调用的队列API
queue: function( type, data ) { // fx doAnimation
var setter = 2; // 存储和获取的标示符
// type为function类型
if ( typeof type !== "string" ) {
data = type;
type = "fx";
setter--;
}
// 说明只传入了type一个参数,获取动画队列
if ( arguments.length < setter ) {
return jQuery.queue( this[0], type );
}
return data === undefined ?
this :
this.each(function() {
// div#aaron "fx" doAnimation
// queue是一个回调函数队列 往缓存数据仓库里添加了 fxqueue属性/值为回调函数队列
var queue = jQuery.queue( this, type, data ); // jQuery内部静态API,供外部API服务
// ensure a hooks for this queue 往缓存数据仓库里添加了 fxqueueHooks属性/值为系统回调函数队列
// 该钩子的作用是当队列函数执行完毕或动画完成的时候,清理剩下的数据,回收内存空间
jQuery._queueHooks( this, type );
// 数据仓库的缓存 {fxqueue:Array[1],fxqueueHooks:Object,olddisplay:block}
// queue[0] = doAnimation
// 如果是fx队列,则会自动出队列操作,inprogress是一个状态机,表示有动画函数正在执行
if ( type === "fx" && queue[0] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
});
},
(1)这个API,既可以存储动画函数(传二个参数),也可以取出动画函数(只传队列名),主要是setter变量,
<pre name="code" class="javascript">// 说明只传入了type一个参数,获取动画队列
if ( arguments.length < setter ) {
return jQuery.queue( this[0], type );
}
(2)
var queue = jQuery.queue( this, type, data );
<pre name="code" class="javascript">// queue是一个回调函数队列 往缓存数据仓库里添加了 fxqueue属性/值为回调函数队列,<span style="font-family: Arial, Helvetica, sans-serif;">jQuery内部静态API,供外部API服务</span>
//内部调用 取出缓存队列
queue: function( elem, type, data ) {
// div#aaron "fx" doAnimation
var queue;
if ( elem ) {
type = ( type || "fx" ) + "queue"; // fxqueue
// 如果之前有fxqueue缓存数据,则获取,没有则直接返回undefined
queue = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
// 如果之前没有缓存数据,则用data数据 生成fxqueue缓存数据
if ( !queue || jQuery.isArray(data) ) {
queue = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
// 有则直接把data压入缓存数据仓库中
queue.push( data );
}
}
// 这个queue就是一个回调函数队列
return queue || [];
}
},
(1)type = ( type || "fx" ) + "queue"; // fxqueue
系统默认动画缓存属性 = fxqueue,如果你传的动画名为myself,那么 缓存属性为 = myselfqueue
(2)queue = jQuery._data( elem, type );
主要在内部数据缓存中取出fxqueue对应的数据(一般doAnimation动画函数),如果之前没有存储过该缓存,那么返回undefined
(3)
if ( !queue || jQuery.isArray(data) ) {
queue = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
// 有则直接把data压入缓存数据仓库中
queue.push( data );
}
如果没有对应的缓存数据,则传进来的data函数,放进fxqueue缓存仓库中,否则如果之前有该缓存的话,直接进行压栈
(4)return queue || [];<pre name="code" class="javascript">最后返回该队列
<pre name="code" class="javascript">jQuery._queueHooks( this, type );
往缓存数据仓库里添加了 fxqueueHooks属性/值为系统回调函数队列,<span style="font-family: Arial, Helvetica, sans-serif;">该钩子的作用是当队列函数执行完毕或动画完成的时候,清理本次动画剩下的数据,回收内存空间,</span>
<span style="font-family:Arial, Helvetica, sans-serif;">上面特意强调下本次动画,应为如果是链式动画的话,当每一个动画完成的时候都会清理一次,即清除当前动画的数据</span>
_queueHooks: function( elem, type ) {
var key = type + "queueHooks"; // fxqueueHooks
return jQuery._data( elem, key ) || jQuery._data( elem, key, {
empty: jQuery.Callbacks("once memory").add(function() {
jQuery._removeData( elem, type + "queue" ); // 清空fxqueue队列数据
jQuery._removeData( elem, key ); // 清空fxqueuehook队列数据
})
});
}
缓存数据 属性= fxqueueHooks,,添加一个callbacks函数对象,调用add将
jQuery._removeData( elem, type + "queue" );
jQuery._removeData( elem, key );两个函数放进回调函数栈中, 动画完成后,激发并执行它们,清理数据(主要是缓存仓库中的动画队列数据)
jQuery.dequeue( this, type );
//
如果是fx队列,则会自动出队列操作,inprogress是一个状态机,表示有动画函数正在执行
//内部调用 从队列最前端移除一个队列函数,并执行
dequeue: function( elem, type ) {
// div#aaron "fx"
type = type || "fx"; // fx
// 先取出队列中的函数
var queue = jQuery.queue( elem, type );
// 用来判断是否已全部出队列
var startLength = queue.length,
// 队列头删除一个
fn = queue.shift(),
// 每次运行一个动画,都要清空一次
hooks = jQuery._queueHooks( elem, type ), // Object {empty: Object}empty: Objectadd: function () {disable: function () {disabled: function () {empty: function () {fire: function () {fireWith: function ( context, args ) {fired: function () {has: function ( fn ) {lock: function () {locked: function () {remove: function () {__proto__: Object__proto__: Object
// 以便能够在回调函数中,循环执行出列操作
next = function() {
jQuery.dequeue( elem, type );
};
// inprogress 当作哨兵 如果是出列操作,总是先删除哨兵占位符
// If the fx queue is dequeued, always remove the progress sentinel
if ( fn === "inprogress" ) {
fn = queue.shift();
startLength--;
}
if ( fn ) {
// Add a progress sentinel to prevent the fx queue from being
// automatically dequeued
// 当从queue队列头部剔除一个回调函数后,想队列头部增加'inprogress'
// 用来做占位符作用,表示有动画函数正在执行
if ( type === "fx" ) {
queue.unshift( "inprogress" );
}
// clear up the last queue stop function
delete hooks.stop;
// 执行函数
fn.call( elem, next, hooks ); // 在这里调用 doAnimation
}
// 如果queue队列出列完毕,则销毁缓存属性fxqueue,fxqueuehooks
if ( !startLength && hooks ) {
hooks.empty.fire();
}
},
I(1)var queue = jQuery.queue( elem, type );<pre name="code" class="javascript">// 先取出队列中的函数队列
var startLength = queue.length,
// 队列头删除一个
fn = queue.shift(),
// 每次运行一个动画,都要清空一次
hooks = jQuery._queueHooks( elem, type ), // Object {empty: Object}empty: Objectadd: function () {disable: function () {disabled: function () {empty: function () {fire: function () {fireWith: function ( context, args ) {fired: function () {has: function ( fn ) {lock: function () {locked: function () {remove: function () {__proto__: Object__proto__: Object
// 以便能够在回调函数中,循环执行出列操作
next = function() {
jQuery.dequeue( elem, type );
};
<pre name="code" class="javascript">fn.call( elem, next, hooks ); // 在这里调用 doAnimation
这里将出列的函数执行,next是迭代dequeue函数,出列几个函数,就执行几次dequeue函数,一次只能出列一个,上面有一个占位符inprogress,当一个函数被出列操作,那么这个会被插入对列,顶戴它的位置,表示正在由动画函数执行中.<pre name="code" class="javascript">hooks 是清理数据的钩子,上面讲过每执行一次动画,就会清理一次,每执行一个动画,就会执行dequeue,出列下一个动画函数,并执行
其实这里的fn就是要执行的动画函数 doAnimation。
今天就到这,接下来就要将每个出队列的动画函数是怎么实现动画的,doAnimation就是入口.