/ Work

[jQuery tech.] 你的 stop() 真的 stop 了嗎 - 漫談 queue() part 2

看過 上一回 的問題之後,我們接著看 queue()dequeue() 在核心中做了甚麼事情:

jQuery.extend({
    queue: function( elem, type, data ) {
        if ( !elem ) {
            return;
        }

        type = (type || "fx") + "queue";
        var q = jQuery.data( elem, type );

        // Speed up dequeue by getting out quickly if this is just a lookup

        /// 一般而言,我們無需特別傳入 data 這個數值陣列,因為 queue() 會自動把 elem 本身的動作排入
        /// 所以,倘若你沒有指定其他的事件時,他就直接返回以 elem 自身動作為主的排程
        /// queue() 你可以把他想成,一種事件能夠依序執行的『排程』

        if ( !data ) {
            return q || [];
        }

        /// 相反的,倘若你指定了其他事件,他就會堆入 queue() 的流程裡面
        /// 值得注意的是,你追加了多少事件,就增加的多少個 queue() 的排程

        if ( !q || jQuery.isArray(data) ) {
            q = jQuery.data( elem, type, jQuery.makeArray(data) );

        } else {
            q.push( data );
        }

        return q;
    },


    dequeue: function( elem, type ) {
        type = type || "fx";

        var queue = jQuery.queue( elem, type ), fn = queue.shift();

        // If the fx queue is dequeued, always remove the progress sentinel

        /// 移除 queue() 的方式很神,他是先斬後奏,他先取出第一個 queue() 然後判斷是不是 "inprogress"
        /// 如果是,繼續往下取,而因為是"一個接著一個"的方式去做,所以下一個儼然就不是 "inprogress"
        /// 如果不是,就沒甚麼好說的,繼續往下做

        if ( fn === "inprogress" ) {
             fn = queue.shift();
         }

        /// fn 有值嗎?當然有,如果沒有表示 queue() 已經被執行完畢。
        /// 如果 type 相符(意同於呼叫一個自訂 queue(),預設是 fx),就在原有的 queue 堆一個 "inprogress" 在前面
        /// 最後回呼下一個 fn,然後再次 dequeue(),達到『先斬後奏』的方法。

        if ( fn ) {
            // Add a progress sentinel to prevent the fx queue from being
            // automatically dequeued
            if ( type === "fx" ) {
                queue.unshift("inprogress");
            }

            fn.call(elem, function() {
                jQuery.dequeue(elem, type);
            });
        }
    }
});

大抵上來說,我們需要停止或是暫停某些效果時,確實是使用 stop(true,true) 是最為妥當的一個方式,更甚是連帶的將你所設定的 queue 排程清空──其實等同於 stop 的第一個 true 的結果──來確保不會發生意外。然而,如果你需要的是暫時停止,那 stop() 的任何設置可能都無法很直覺的使用

所以,倘若你的動態效果超過兩個步驟以上,需要可以中途停止並且能繼續執行,請不要使用 . 連字號來使用 queue() 與 dequeue() 這兩個方法,除非你 不要停!不要停!不要停! 不想停!不想停!不想停!老師叫你停你都沒有再聽,你看看現在停不下來了吧(丟滑鼠)。

  • 原生 queue()dequeue() 因為先斬後奏,所以要由原生 jQuery 來做到是不可能的。
  • 使用 . 連字號也是不可暫停的。

那我要怎麼暫停?

  • 將動態效果拆入陣列中,當作是 data 傳入 queue()

  • 將動態效果陣列另存成一個陣列(例如:myQueue),他是可變動的。

  • 呼叫 dequeue() ──動態效果開始的每一個步驟──之前,紀錄下陣列鍵值。

  • 請用下列方式去調停 queue()

var $box = $('#div-a');
$box.queue([]);
$box.each(function() {
    var timers = jQuery.timers;
    // go in reverse order so anything added to the queue during the loop is ignored
    for ( var i = timers.length - 1; i >= 0; i-- ) {
        if ( timers[i].elem === this ) {
            timers.splice(i, 1);
        }
    }
});
  • 修改自原生 stop() 區塊,但是不強制使用 dequeue(),這是唯一的區別。當在次按下開始的時候,將 myQueue 中,小於該步驟紀錄鍵值的 data 全數 shift() 掉,再將這個新的 myQueue 傳入。
  • 往後的步驟都是這樣做,直到整個 myQueue 被執行完畢。

範例呢?在電鍋裡,請自己盛!