1. 程式人生 > >jQuery動畫高階用法(上)——詳解animation中的.queue()函式

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');

並且我們將程式碼付諸於實踐,如下所示:

origin object goal

本例完整程式碼:

當你執行之後會發現,結果並不如我們希望的那樣。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()。

修改程式碼後,就能達到我們要的效果了,如下所示:

container object goal

本例完整程式碼:

.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()的函式定義,其實我們現在這種在queue中加入函式的用法,官方給出的函式原型是:

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)<br>               .slideDown(1000)<br>               .animate({width: '50px'}, {duration: 1000});

執行後它只會按照順序來執行,先收起,再放下,再把寬度收縮為50px

但是一旦我加入了"queue:false"這個引數:

$('#section3a').slideUp(1000) .slideDown(1000) .animate({width: '50px'}, {duration: 1000, queue: false});
你會發現在收縮放下的同時,object的寬度也在收縮

本來線性執行的slideUp,slideDown,animate,變成了animate和slideUp,slideDown並行:

執行結果如下

本例完整程式碼:

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的錯誤了吧……):

true false

本例完整程式碼:

好的,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

點選按鈕就可以看見實時佇列的長度

本例原始碼:

替換佇列

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()函式,也是我栽過跟頭的地方。