jQuery动画高级用法(上)——详解animation中的.queue()函数
如果你拿着一个疑问去找专业人士寻找答案,那么你的一个疑问会变成三个,因为他会用另外两个令你更加一头雾水的名词来解释你的这个疑问。
我想这是大多数,包括我在内,IT人在学习过程中碰到的最大问题。当你有一段代码或是一个概念不是很清楚,百度也好,Google也好,在论坛发问也好,给出的答案往往又会夹杂着更多你不懂得概念和令你头疼的代码。
我亦是吃了同样的亏,痛定思痛,决定对animate方面做一些总结,希望能给大家一些启发和帮助
从一个实际应用谈起
今天不谈animate()、fadeIn()、fadeOut()、slideUp()、show()、hide()诸如此类的具体动画函数,而谈谈几个并不常用的,甚至说是有点风马牛不相及,但又十分十分重要的动画函数queue(),dequeue(),和stop()。
先让我们从一个简单的例子谈,假设有一个购物功能,在结账之前,用户仍然可以把购物车里的删除至备选栏中(也许因为用户的资金不足,可以存储至下次购买)
好,根据以上描述的需求,我们抽象了以下的布局,"origin"模拟购物车,"goal"模拟备选栏,"object"模拟那个可能被删除的物品,而"change"按钮实现可以把物品从左右的交换功能
同时我们希望当物品从左交换至右的过程中,加入动画效果,我们希望是一个这样过程:在左侧先有一个hide()隐藏效果,告诉用户物品已经不再购物篮中,然后前端用appendTo()把物品移动至右侧,在用一个show()效果告诉用户已经转移成功了。流程如下
(在左侧)object隐藏——>object从左侧转移到右侧——>object显现(在右侧)
于是很自然我们得出了以下代码:
$('#object').hide('slow').appendTo($('#goal')).show('slow');
并且我们将代码付诸于实践,如下所示:
本例完整代码:
$('#test-change').toggle(function(){ $('#test-object').hide('slow').appendTo($('#test-goal')).show('slow'); },function(){ $('#test-object').hide('slow').appendTo($('#test-origin')).show('slow');
当你运行之后会发现,结果并不如我们希望的那样。object会先发生转移,再隐藏,再出现:
object从左侧转移到右侧 ——>object隐藏(在右侧)——>object显现(在右侧)
所以问题是,明明是按我们预定的顺序书写的,但结果却不如期望的那样,这是为什么?
这也是我在自己项目中第一个接触queue()的实际例子,在jQuery的官方论坛发帖询问原因后(原帖在这里),以下这段回答是关键
when you are using methods that animate content, those animations are added to what is called a queue, specifically the "fx" queue. normal jquery methods, such as prependTo(), are not added to the "fx" queue, therefore they get executed immediately instead of waiting till the previously added item in the queue is executed.
什么意思呢?也就是说,当你使用一系列的动画效果(如hide,show),这些动画函数都会被放进一个名为"fx"的队列中,然后在以先进先出的方式执行队列中的函数,而非动画函数,比如上面例子中的appendTo函数,则是不会进入这个队列中,并且先于动画函数的执行,也就是在"fx"先进先出,取出第一个函数之前,它就已经执行了。
所以在上面的例子中,我们可以看到object被先转移到了右侧中,然后再隐藏再显现,也就是appendTo函数先执行于动画函数的结果
了解病根之后,我们需要对症下药——说到底只是个顺序的问题,只要让appenTo后于hide先于show执行就万事大吉了。于是我们想,可不可以也让appendTo函数加入队列并且位于hide和show之间?这样就能按顺序执行了
答案是肯定了,虽然"fx"队列默认情况下是储备动画的函数,但加入了manipulation函数也没有什么不可以——准确来说不仅仅是manipulation函数,jQuery为我们提供了queue()函数,来把你需要的某些代码插入到某个队列中。为什么说某个?因为在后面的例子中我们可以看到其实还可以自定义队列的。
我们先把上面例子改成我们希望的效果,再根据代码来讲解queue的方法
$('#object').hide('slow').queue(function(next){ $(this).appendTo($('#goal')); next(); }).show('slow');
其实看代码就一目了然。我们可以这么理解(只是有助于理解它的功能,并非它的实质),queue()就是不乖的,帮助插队的函数,你想让某个功能或某一系列功能插入几个动画之间,就把这一系列你想插入的功能函数放入queue()的函数体中,就向上面的代码一样,而参数"next"和next()则是保证再执行完这个插入的函数后,能继续能执行队列中的下一个动画函数,在上面的例子中也就是show()。
修改代码后,就能达到我们要的效果了,如下所示:
本例完整代码:
$('#test-change1').toggle(function(){ $('#test-object1').hide('slow').queue(function(next){ $('#test-object1').appendTo($('#test-goal1')); next(); }).show('slow'); },function(){ $('#test-object1').hide('slow').queue(function(next){ $('#test-object1').appendTo($('#test-origin1')); next(); }).show('slow'); });
.queue()初探
接下来我们正经谈谈queue函数
我们还是从一个简单的例子说起:
假如你要让一个黑色背景的小方块div,先收起(slideUp),在放下(SlideDown),背景再变成白色,语句应该怎么写?
吸取了上个例子的教训,相信没有会很天真的按顺序写出这样的语句了吧?
$('div').slideUp('slow').slideDown('slow').css({"background":"red"});
应该怎么写呢?使用queue函数!brilliant!
$('div').slideUp('slow').slideDown('slow').queue(function(next){ $('#object').css({"background":"red"}); next(); });
实际例子就不在页面上展示了,这是一段很简单的代码,应该可以想象得到吧。
在这里我想说明几个问题:
首先,jQuery官方在阐述.queue这个方法的时候有这么一句话很有趣:
This feature is similar to providing a callback function with an animation method,
but does not require the callback to be given at the time the animation is performed.
queue( [ queueName ], callback( next ) )
也就是说我们加入的函数其实是一个关于队列的回调函数。也就是在队列结束之后,系统会自动为你调用你加入的函数。
插一句话,究竟什么是回调函数?百度一下,你就知道。返回结果的第一条就是百度百科关于“回调函数”的解释。但是正如本文章开头所说,它的确给了你很详细很详细的解释,但前提是你能消化那些C++专业词汇和代码……幸运的是我的Unix网络编程老师(嘿,一位来自北大的博士)曾经给过我们一个很通俗的解释,自己定义,系统调用。回调函数的关键在于我们无法预知它何时被调用。因为我们只是定义了这么一个函数,可能通知系统在完成某一系列的动作后来调用它。
其实我们可以这样考虑,如果把这个函数作为slideDown的回调函数效果不都是一样的吗?因为我们最终想要的只是保证变色函数在slideDown之后执行,slideDown和queue的回调函数都能保证这种效果!look:
$('div').slideUp('slow').slideDown('slow',function(){ $('#object').css({"background":"red"}); });
正是有异曲同工之妙。
还有一点需要注意的是.queue()中的next参数和next()能不能舍去其一或是?
我们上面说到queue中的函数是回调函数,如果我们稍稍对上上面的代码做一些修改,比如:
$('div').slideUp('slow').slideDown('slow').queue(function(next){ $('#object').css({"background":"red"}); //next(); }).hide('slow');
一是我把next()语句注释掉了,二是希望在变色以后再让方块隐藏起来。但是当你运行之后,发现在变色之后无法对方块执行隐藏。
要记住queue中的函数是回调函数呀,默认情况下只有动画队列执行完了,才会调用变色函数,既然动画队列都执行完了,哪里来的hide()?所以next()是保证在执行完这次队列后再次执行下一个动画函数
我曾经尝试过抛弃next参数而保留next()语句,这样的结果是能在现代浏览器(firefox,chrome之类)中运行,但无法在ie6中运行。所以,保留吧 。next和next()是jquery1.4中才开始出现的,而在之前使用的是.dequeue()函数,如果要将这节的例子改为使用dequeue(),如下:
$('#object').slideUp('slow').slideDown('slow').queue(function(){ $('#object').css({"background":"red"}); $(this).dequeue(); });
自定义队列
我之前有提过其实可以不使用它默认的'fx'队列,这节就教大家怎么自定义一个属于自己的队列,很简单:
我想建立一个名为'custom'的队列,里面有一个能使黑色小方块改变背景颜色的方法,如下:
$("div").queue("custom", function(next) { $('div').css({'background':'red'}); next(); });
所见即所得——前面一个'custom'代表新队列的队列名(要是我也取'fx'会怎么样?我也不知道,有兴趣的朋友尝试之后可以留言告诉我结果,我也有兴趣会知道),后面仍然是回调函数,把你想执行的功能堆砌进去。
但就这段代码而已,待你真正添加进网页,并且尝试运行,会发现并非“所见即所得”,压根就不会有任何效果。因为我故意省略了一段最最关键的语句……修改后的如下:
$("div").queue("custom", function(next) { $('div').css({'background':'red'}); next(); }) .dequeue("custom"); //this is the key
对,就是这句,.dequeue("custom")。一般对与dequeue()的定义是“删除队列中最顶部的函数,并且执行它”。我并不赞同用“删除”这个字眼,而是倾向于“取出”,其实这个函数的功能就好像是一个数据结构中队列的指针,待队列中前一个函数执行完后,取下一个队列最顶端的函数。
实战
OK,主要的几个知识点都介绍完了。或许你会问,我们真的会用到这么复杂的动画操作吗呢?我相信大多数人士不会的,动画在网页中只是小小的点缀,但是小心驶得万年船。在这里我就照搬Cameron Mckay博客上的关于队列应用的例子
假设你要这么一个效果:让一个物体向上浮动2000毫秒(2秒),并且在前1000毫秒物体完全不透明,而在后1000毫秒物体从完全不透明变成完全透明,为了解释的更清楚,给出了下面的这个时间轴表(假设物体开始在距容器顶部100px的位置,也就是top:100px;):
时间(毫秒) | 距顶端高度 | 不透明度 |
---|---|---|
0 | 100px | 1.0 |
500 | 90px | 1.0 |
1000 | 80px | 1.0 |
1500 | 70px | 0.5 |
2000 | 60px | 0.0 |
从时间轴表中我们可以更清晰的看到我们想要的效果,在这2000毫秒的时间内,物体的高度一致在均匀变化,逐渐减小,而不透明度在前1000毫秒始终保持为1.0,而在后1000毫秒才逐渐减小直至完全为0。
如果我们暂且只考虑向上浮动和透明效果,我们可能会写出这样的语句:
$("#object").animate({opacity: 0, top: "-=40"}, {duration: 2000});
很遗憾,这样的语句只能让物体在整体2000毫秒中都处于逐渐向不透明转化的过程,也就是不能让它在前1000毫秒中保持100%不透明——于是我们用queue来解决这个问题:
$("#object") .delay(1000, "fader") .queue("fader", function(next) { $(this).animate({opacity: 0}, {duration: 1000, queue: false}); next(); }) .dequeue("fader") .animate({top: "-=40"}, {duration: 2000})
我们先来看它的思路:把控制不透明度和控向上移动的动画分别存储在两个队列中,控制向上移动的队列按默认情况进行(在2000毫秒内完成),而不透明度的控制在1000毫秒内执行,但这个队列要晚于默认队列1000毫秒执行
再简单一点,就是:前1000毫秒,只有控制高度的“fx”队列执行,而后1000毫秒,控制不透明度的“fader”队列和控制高度的“fx”并行
首先准备两个队列,
一个是默认的"fx",存储高度变化动画:
.animate({top: "-=40"}, {duration: 2000})
用来另一个是自定义的"fader"的队列,来存储不透明度变化的动画:
.animate({opacity: 0}, {duration: 1000, queue: false});
注意上面这段代码中的"queue:false",这是很关键的一句话,目的是让这个animate不进入默认的"fx"队列中
任何的动画效果都会进入"fx"队列中,即使你定义在.queue()中的动画也是一样,并且动画效果,务必会按顺序执行,比如说下面这段代码:
$('#object').slideUp(1000)
.slideDown(1000)
.animate({width: '50px'}, {duration: 1000});
运行后它只会按照顺序来执行,先收起,再放下,再把宽度收缩为50px
但是一旦我加入了"queue:false"这个参数:
$('#section3a').slideUp(1000) .slideDown(1000) .animate({width: '50px'}, {duration: 1000, queue: false});
本来线性执行的slideUp,slideDown,animate,变成了animate和slideUp,slideDown并行:
运行结果如下
本例完整代码:
function queuetrue(){ $('#section3a').slideUp(1000) .slideDown(1000) .animate({width: '50px'}, {duration: 1000}); } function queuefalse(){ $('#section3a').slideUp(1000) .slideDown(1000) .animate({width: '50px'}, {duration: 1000, queue: false}); } $("#section3a-input").click(function(){ $("#section3a").css({'width':'100px'}); queuetrue(); }); $("#section3b-input").click(function(){ $("#section3a").css({'width':'100px'}); queuefalse(); });
OK,我们回过头来再看实战中的这个例子:
$("#object") .delay(1000, "fader") .queue("fader", function(next) { $(this).animate({opacity: 0}, {duration: 1000, queue: false}); next(); }) .dequeue("fader") .animate({top: "-=40"}, {duration: 2000})
其实前三个语句(这里所说的语句以"."为区分标志),做了这么几件事:
定义一个名为fader的队列,专用于控制不透明度的改变——.queue()语句
让它1000毫秒后执行——.delay()延时函数,延时fader队列的执行时间;.dequeue执行fader队列。
而最后的.animate则是默认进行的,不用管它。一起来看看效果,左边的是正确的,右边的是错误的(可能在IE6中有布局错位的情况,因为是jQuery例子,时间有限,也就不追究css的错误了吧……):
本例完整代码:
function animateR(){ $("#section4object") .delay(1000, "fader") .queue("fader", function(next) { $(this).animate({opacity: 0},{duration: 1000, queue: false}); next(); }) .dequeue("fader") .animate({top: "-=40"}, {duration: 2000}) } function animateU(){ $("#section4object2").animate({opacity: 0, top: "-=40"}, {duration: 2000}); } $("#section4start").click(function(){ $("#section4object,#section4object2").css({'top':'100px','opacity': '1'}).show(); animateR(); animateU(); });
好的,queue的主要功能就介绍到这里,下面还有点时间,介绍一些非主流功能:
获取队列长度
比如用队列名取得匹配元素的长度:
var $queue=$("div").queue('fx');
很明显,就是取得队列名为'fx'的队列,如果想取得长度的话:
var $length=$('div').queue('fx').length;
注意这里的队列长度只是匹配元素还未运行的队列长度,当动画运行完之后,队列长度会自动归为0,举下面一个例子:
function animateT(){ $("#section2-div").slideToggle('3000') .slideToggle('3000') .hide('3000') .show('3000') .animate({left:'+=200'},2000) .hide('3000') .show('3000') .animate({left:'-=200'},2000,animateT);//在这轮动画结束的时候再调用自己,使动画无限循环下去 }
然后当点击按钮的时候显示队列的长度:
$("#section2-input").click(function(){ var $queue=$("#section2-div").queue('fx'); $('#section2-h1').text($queue.length); });
效果:
0
点击按钮就可以看见实时队列的长度
本例源码:
function animatelength(){ $("#section2-div").slideToggle('3000') .slideToggle('3000') .hide('3000') .show('3000') .animate({left:'+=200'},2000) .hide('3000') .show('3000') .animate({left:'-=200'},2000,animatelength); } $("#section2-input").click(function(){ var $queue=$("#section2-div").queue('fx'); $('#section2-h1').text($queue.length); });
animatelength();
替换队列
queue还有一种用法是替换队列,就是自定义一个队列后,用自定义的队列替换元素原匹配的队列:
比如你给某个元素定义了两个队列:
$('div').queue('fx',function(){ $('div').slideDown('slow') .slideUp('slow') .animate({left:'+=100'},4000); });//定义fx $('div').queue('fx2',function(){ $('div').slideDown('fast') .slideUp('fast') .animate({left:'+=100'},1000); });//定义fx2
这里定义了两个队列,一个是慢队列,也就是默认的'fx',另一个是快队列'fx2'
当点击某个按钮时:
$('input').click(function(){ $('div').queue('fx',fx2); });
很简单吧,明显用fx2替换了fx,当然这也不是立即替换,如果fx还没有执行完的话。除非你用stop()函数(我们下节课介绍)。
总结
OK,今天对queue 的讲解就到这里,肯定有很多疏漏的地方,希望大家多多指证,上面的这些用法是我总结出来,应该算是比较主流的用法吧。如果还有一些我没有提到,或是有什么问题想交流,都可以留言给我。
参考的资料有jQuery官方文档说明 ,Cameron McKay的博客,《犀利开发jQuery》
下节课我打算向大家介绍stop()函数,也是我栽过跟头的地方。