基於setTimeout製做滾動廣告板

不少網站在其門戶頁面的上方正中央都會放置一個滾動廣告板,用於顯示一些推薦信息,用戶點擊便可進入瀏覽。比較常見的就是各個公司的官網,電商網站的首頁等。
下面是天貓的滾動廣告板截圖。
tmall滾動廣告版css

其實,不須要藉助於什麼複雜的技術,僅僅經過setTimeout函數,就能實現這種滾動廣告板的效果。css3

分析

仔細觀察這個滾動廣告版,咱們發現,其功能無非就兩點:瀏覽器

  • 無限循環的不停滾動顯示每一條廣告
  • 當點擊某一個廣告的索引時,以那條廣告爲起點,無限循環滾動

先分析一下第一個功能點。
要作到無限循環的不停滾動顯示每一條廣告,那麼只須要關注兩個相鄰廣告頁的切換(暫且稱爲函數doSwitch),而後經過設置定時任務不停的運行doSwitch就能夠了。所以,咱們有了一個基本的雛形:函數

function doSwitch() {
    //switch adjacent ad pages
}  

function autoSwitch() {
    doSwitch();
};

setTimeout(autoSwitch, someInterval)

如今,咱們只須要重點關注switch的實現就能夠了。
上面截圖中,廣告頁經過淡入淡出效果來進行切換,很容易就聯想到css3中的opacity屬性(天貓也是這麼實現的),可是考慮到IE8的存在,opacity就不適用了,因爲具體的切換效果與今天的主題關係不大,因此改爲了橫向滾動切換效果。
橫向滾動有不少種實現效果,最直接的方式,就是經過margin-left來實現。設廣告頁的寬度爲w,以當前廣告頁左邊的邊框爲基準線,將當前廣告頁的margin-left由0逐漸遞減至-w,將下一廣告頁的margin-left由w逐漸減至0(廣告板的父容器設置overflow:hidden)。所以,doSwitch能夠簡單實現以下:組件化

function doSwitch() {
    var curPage = document.getElementById(curPageId),
        nextPage = document.getElementById(nextPageId);
    curPage.style.marginLeft -= step;   // 此處忽略了單位處理
    nextPage.style.marginLeft -= step;

    if (nextPage.style.marginLeft > 0) {
        setTimeout(doSwitch, someInnerInterval);  
    } else {
        setTimeout(autoSwitch, someInterval);
    }
}

因爲切換效果的時間應該遠小於廣告停留的時間,因此此處someInnerInterval小於someInterval.網站

注意到,每次廣告頁的切換,廣告頁下方的索引按鈕也會跟着切換,所以,上方的autoSwitch函數中還要增長一項設置當前選中索引的功能,暫且成爲setCurIndex函數,則autoSwitch實現修改以下:線程

function autoSwitch() {
    setCurIndex();
    doSwitch();
}

到此,無限循環滾動顯示廣告的功能就完成了.code

上面的代碼只作基本原理介紹,實際操做過程當中還有不少細節須要處理。blog

再看第二個功能點。
當用戶點擊某個廣告頁的索引時,當前廣告會當即被切換到選中的廣告頁,並從選中的廣告頁開始循環滾動。咋一看跟功能點一很像,彷佛只須要在用戶點擊某個索引的時候將當前廣告頁設置爲用戶選中的廣告頁就能夠了。可是不少時候,最直接的想法每每是不對的,這裏須要考慮到setTimeout的原理。
setTimeout函數的正確使用姿式應該像下面的代碼同樣:排序

var timeout = setTimeout(function () {
    //do something after at least 1s
}, 1000);

// clearTimeout(timeout);    //cancel setTimeout handler function

咱們知道,JavaScript是單線程的,定時器只是計劃代碼在將來的某個時間點執行,可是,執行時機是不能保證的。在頁面的生命週期中,不一樣時間可能有其餘代碼控制着JavaScript線程。例如在頁面下載完後的代碼運行、事件處理程序、Ajax請求的回調函數等都必須使用一樣的線程來執行。實際上,瀏覽器負責進行排序,指派某段代碼在某個時間點運行的優先級。在瀏覽器內部管理着一個代碼隊列,隨着頁面生命週期的推移,代碼會按照執行順序添加入隊列。例如,當某個按鈕被按下時,它的事件處理程序代碼就會被添加到隊列中,並在下一個可能的時間裏執行。當接到某個Ajax響應時,回調函數的代碼會被添加到隊列。瀏覽器經過事件循環的機制從這個隊列中取出處理函數代碼並執行。
瀏覽器事件循環
定時器對隊列的工做方式是,在指定時間過去後,將代碼插入隊列。請注意,給隊列添加代碼並不意味着它會當即執行,而只能表示它會盡快執行。與setTimeout函數成對出現的是clearTimeout函數,用於取消setTimeout設置的延時執行的函數。該函數只接受一個參數,那就是setTimeout返回的延時調用ID。

回到剛纔的問題中,功能一中,經過setTimeout函數,不停的將doSwitch和autoSwitch函數添加到隊列中,若是當用戶點擊某個廣告頁索引時,只是簡單的將當前廣告設置爲選中的廣告頁,而後調用autoSwitch的話,將會致使兩個無限循環滾動同時運行,這必將致使廣告頁顯示的混亂。
那麼如何解決這個問題呢?
當用戶點擊某個索引時,咱們只須要終止以前一直運行的循環滾動,而後開啓新的循環滾動便可。那麼問題就聚焦在如何終止上一個循環滾動上面來了。實際上,咱們能夠經過clearTimeout來實現。咱們只須要在doSwitch中添加一個狀態檢測,若是用戶當前點擊了某個索引,則退出函數執行。樣例以下:

function doSwitch() {
    if (paused) {
        return;
    }

    // other 
}

這時候,咱們只須要在用戶點擊某個廣告索引時,設置paused爲true,而後在新一輪的循環滾動開始前將paused設置爲false就能夠了。代碼以下:

indexDom.onclick = function () {
    setCurIndex();
    paused = true;
    
    setTimeout(function () {
        paused = false;
        autoSwitch();
    }, someInterval);
}

須要注意的一點是,這裏的someInterval與功能點一中的someInterval要保持相同。因爲someInnerInterval小於someInterval,因此在下一個循環滾動開始以前,上一個循環滾動已經中止了。

到此,實際上基本功能就已經結束了,可是在完成基本功能以後,咱們還要照顧到異常場景的存在。這裏就須要處理短期內連續點擊不一樣索引的狀況。
其實也很簡單,只須要在onclick時間處理函數中保存一個curClick索引,而後在延時函數中比對curClick是否與當前的curIndex一致就能夠了。
因此上面的onclick能夠改寫以下:

indexDom.onclick = function () {
    var curClick = indexDom["data-index"];  //get the index of indexDom    
    setCurIndex();
    curIndex = curClick;    
    setTimeout(function () {
        if (curIndex !== curClick) {
            return;
        }
        paused = true;
        setTimeout(function () {
            paused = false;
            authSwitch();
        }, someInterval)
    }, 0);
}

到此,整個滾動廣告板就作完了,可是實現功能僅僅是第一步,下一步還須要將其組件化,將一些內部狀態隱藏,抽象出組件接口。在使用的時候,只須要實例化一個組件實例,而後使用必要的數據調用組件接口就能夠了。
下面是實現的效果圖:
滾動廣告板

源代碼稍後放出:)

相關文章
相關標籤/搜索