jQuery 動畫

原文轉載自:http://www.cnblogs.com/zhwl/p/4328279.html

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
 

本例完整代碼:

$('#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()。

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

container
object
goal
 

本例完整代碼:

 

$('#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()的函數定義,其實我們現在這種在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);
});

效果:

4

 
 

點擊按鈕就可以看見實時隊列的長度

本例源碼:

 

 

替換隊列

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章