共计 4633 个字符,预计需要花费 12 分钟才能阅读完成。
前言:
需要先看 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()
的流程图 为: