手把手和你用原生JS寫一個循環播放圖片輪播

前段時間學習了淘寶首頁的靜態頁面,其中收穫較大的的就是這個循環播放的圖片輪播組件,本文就將相關製做經驗分享給你們。javascript

先看看在線DEMO:原生JS循環播放圖片輪播組件 (支持IE8+,本文中的在線demo均未通過壓縮,能夠直接在瀏覽器中調試css

以及GitHub倉庫地址及完整代碼:JuniorTour/simple-standard-js-carouselhtml

1、思路講解:

1.先說基本的非循環無過渡圖片輪播:

這個思路仍是很簡單的,經過觀察一些圖片輪播就能夠發現,圖片輪播通常是以一個尺寸較小的父元素做爲窗口,包裹住一組較長的長條狀的項目(item)子元素,再利用 overflow: hidden; ,將父元素做爲「窗口」,只顯示出的項目子元素的一部分,並經過改變項目子元素的定位或translate3d屬性,實現多張圖片項目動態播放。java

基本原理能夠參考這個demo:圖片輪播基本原理演示git

2.比較有意思的實際上是循環的功能:

可是這樣簡單的輪播是不會循環播放的,也就是說當一輪圖片項目(item)播放到結尾;或者當在第一張圖(第一個項目)繼續向前時,就會超出內容子元素,出現空白部分,這通常不是咱們想要的結果。github

有多種思路能夠實現循環播放,我觀察到淘寶網首頁的圖片輪播是這樣的思路:bootstrap

複製開頭和結尾的項目,並分別放在開頭和結尾,當播放到開頭或結尾的項目,繼續播放,須要循環時,臨時取消transition屬性,並當即用定位跳轉至相應的真正的開頭或結尾以後,再恢復原來的transition,繼續正常滾動播放,從而利用視覺上的「欺騙」,實現帶有過渡效果的循環播放。瀏覽器

相應的原理能夠參考這個demo:圖片輪播循環原理演示閉包

2、HTML標記部分

核心理念是簡潔、語義化。這部分由於我學過bootstrap框架因此借鑑了bootstrap的HTML標記結構app

總體結構爲:

外層的.carousel-wrapper包裹着輪播的三個主要部分,分別是:

.carousel-item-wrapper:項目內容部分(做爲演示,本文中的demo使用a標籤代替了圖片,你們能夠自行嘗試替換爲圖片;同時添加了文字序號標記,以便於觀察理解,尤爲要注意兩個複製的開頭和結尾項目copy-1和copy-5)

.carousel-control-wrapper:控制按鈕部分,即兩個用於控制左右移動的按鈕。

.carousel-index-wrapper:索引按鈕部分,即圖片輪播中的那一排「小圓點」。爲了便於用JS操控,我添加了id做爲「鉤子」。而bootstrap在這裏用的是自定義的data屬性。

<div class="carousel-wrapper">
    <div class="carousel-item-wrapper" style="left: -520px;">
        <div class="carousel-item">
            <a href="#">
                <!--做爲演示,用a標籤代替了圖片。-->
                <!--<img src="img/carousel-img-5" alt="">-->
            </a>
            <div class="carousel-index-mark">
                copy-5
            </div>
        </div>
        <div class="carousel-item">
            <a href="#">
                <!--<img src="img/carousel-img-1" alt="">-->
            </a>
            <div class="carousel-index-mark">
                1
            </div>
        </div>
        <div class="carousel-item">
            <a href="#">
                <!--<img src="img/carousel-img-2" alt="">-->
            </a>
            <div class="carousel-index-mark">
                2
            </div>
        </div>
        <div class="carousel-item">
            <a href="#">
                <!--<img src="img/carousel-img-3" alt="">-->
            </a>
            <div class="carousel-index-mark">
                3
            </div>
        </div>
        <div class="carousel-item">
            <a href="#">
                <!--<img src="img/carousel-img-4" alt="">-->
            </a>
            <div class="carousel-index-mark">
                4
            </div>
        </div>
        <div class="carousel-item">
            <a href="#">
                <!--<img src="img/carousel-img-5" alt="">-->
            </a>
            <div class="carousel-index-mark">
                5
            </div>
        </div>
        <div class="carousel-item">
            <a href="#">
                <!--<img src="img/carousel-img-1" alt="">-->
            </a>
            <div class="carousel-index-mark">
                copy-1
            </div>
        </div>
    </div>
    <div class="carousel-control-wrapper">
        <button id="prev">
            <!--prev-->
            <i>&lt;</i>
        </button>
        <button id="next">
            <!--next-->
            <i>&gt;</i>
        </button>
    </div>
    <div class="carousel-index-wrapper">
        <ul>
            <li class="carousel-index-btn active-carousel-index-btn" id="carousel-to-1">carousel-index-1</li>
            <li class="carousel-index-btn" id="carousel-to-2">carousel-index-2</li>
            <li class="carousel-index-btn" id="carousel-to-3">carousel-index-3</li>
            <li class="carousel-index-btn" id="carousel-to-4">carousel-index-4</li>
            <li class="carousel-index-btn" id="carousel-to-5">carousel-index-5</li>
        </ul>
    </div>
</div>

3、CSS樣式部分

總的來講比較簡單,重要的地方我加上了註釋,有存疑的地方,歡迎和我交流。

/*reset*/
    * {
        border: none;
        padding: 0;
        margin: 0;
    }
    button {
        outline: none;
    }
    li {
        list-style: none;
    }

    .carousel-wrapper {
        width:520px;
        height:280px;
        overflow: hidden;   /*關鍵*/
        position: relative;
        margin: 100px auto;
    }
    .carousel-item-wrapper {
        width:3640px;
        height:280px;
        position: absolute;
        top: 0;
        left: -520px;
        transition: left .2s ease-in;
    }
    .carousel-item a {
        display: block;
        background-color: red;
        width:520px;
        height: 280px;
    }

    /*使用不一樣背景色的a替代圖片。*/
    .carousel-item:nth-child(1) a {
        background-color: rgb(129,194,214);
        /*第五張圖片的複製*/
    }
    .carousel-item:nth-child(2) a {
        background-color: rgb(129,146,214);
    }
    .carousel-item:nth-child(3) a {
        background-color: rgb(217,179,230);
    }
    .carousel-item:nth-child(4) a {
        background-color: rgb(220,247,161);
    }
    .carousel-item:nth-child(5) a {
        background-color: rgb(131,252,216);
    }
    .carousel-item:nth-child(6) a {
        background-color: rgb(129,194,214);
    }
    .carousel-item:nth-child(7) a {
        background-color: rgb(129,146,214);
        /*第一張圖片的複製*/
    }

    .carousel-item {
        float: left;
    }
    .carousel-index-mark {
        font-size:60px;
        color: black;
        position: absolute;
        top: 0;
    }
    .carousel-control-wrapper {
        transition: all .2s;
    }
    .carousel-wrapper:hover button {
        display: block;
    }
    .carousel-control-wrapper button {
        transition: all .2s linear;
        display: none;
        width:24px;
        height:36px;
        line-height:36px;
        background-color: rgba(0,0,0,.3);
        color: #fff;
        position: absolute;
        top: 50%;
        cursor: pointer;
    }
    button#prev {
        left:0;
    }
    button#next {
        right:0;
    }
    button i {
        font-size: 18px;
    }
    .carousel-index-wrapper {
        width:65px;
        height:13px;
        overflow: hidden;
        position: absolute;
        bottom:15px;
        left:50%;
        margin-left: -33px;
    }
    .carousel-index-btn {
        width:9px;
        height:9px;
        float: left;
        margin:2px;
        background-color: #b7b7b7;
        border-radius: 50%;
        text-indent: -999em;
        /*這個-999em的文字對齊聲明有助於加強可訪問性。*/
        cursor: pointer;
    }
    .active-carousel-index-btn {
        background-color: #f44103;
    }

4、JS部分

這一塊是主要部分,內容較多,所以咱們逐步來實現各部分功能以便於理解。

0.功能和結構分析:

根據最開始的思路講解,咱們把這個輪播的JavaScript功能大體分爲如下4個部分:
1.左右滑動按鈕功能
2.索引按鈕跳轉功能
3.自動播放功能
4.循環播放功能。

咱們來分別逐步實現。

1.實現左右滑動按鈕功能:

function addLoadEvent(func) {
    var oldLoad = window.onload;
    if (typeof oldLoad != 'function') {
        window.onload = func;
    } else {
        window.onload = function () {
            oldLoad();
            func();
        }
    }
}
//給文檔加載完成後的load事件綁定相應的處理函數:
addLoadEvent(preventDefaultAnchors);
addLoadEvent(carouselControl);

/*用一個對象把輪播組件的相關參數封裝起來,優勢是靈活便於擴展升級;缺點是同時也增長了文件的體積。*/
var carouselInfo = {
    itemWidth: 520,
    trueItemNum: 5,
    itemNum: 7,
    totalWidth: 7 * 520
};

//阻止a標籤默認的點擊跳轉行爲
function preventDefaultAnchors() {
    var allAnchors = document.querySelectorAll('a');

    for (var i = 0; i < allAnchors.length; i++) {
        allAnchors[i].addEventListener('click', function (e) {
            e.preventDefault();
        }, false);
    }
}

function carouselControl () {
    var prev = document.querySelector("#prev");
    var next = document.querySelector("#next");
    var carouselWrapper = document.querySelector(".carousel-wrapper");

    prev.onclick = function () {
        slide(-1);
    };
    next.onclick = function () {
        slide(1);
    };
}

function slide(slideItemNum) {
    var itemWrapper=document.querySelector(".carousel-item-wrapper");
    var currentLeftOffset=(itemWrapper.style.left)?parseInt(itemWrapper.style.left): 0,
        targetLeftOffset=currentLeftOffset-(slideItemNum*carouselInfo.itemWidth);

    itemWrapper.style.left=targetLeftOffset+'px';
}

第1步的demo:carousel-step-1

2.實現索引按鈕跳轉功能:

function carouselControl() {
    var prev = document.querySelector("#prev");
    var next = document.querySelector("#next");
    var carouselWrapper = document.querySelector(".carousel-wrapper");
    //添加索引按鈕的引用
    var indexBtns = document.querySelectorAll(".carousel-index-btn");

    //標記當前所在的圖片編號,用於配合控制.index-btn。
    var currentItemNum = 1;
    prev.onclick = function () {
        //把滑動功能和切換索引按鈕功能裝入一個函數之中,以便於獲取當前索引:
        currentItemNum=prevItem(currentItemNum);
    };
    next.onclick = function () {
        //把滑動功能和切換索引按鈕功能裝入一個函數之中,以便於獲取當前索引:
        currentItemNum=nextItem(currentItemNum);
    };

    for (var i = 0; i < indexBtns.length; i++) {
        //利用當即調用函數,解決閉包的反作用,傳入相應的index值
        (function (i) {
            indexBtns[i].onclick = function () {
                slideTo(i+1);
                currentItemNum=i+1;
            }
        })(i);
    }
}

function nextItem(currentItemNum) {
    slide(1);
    currentItemNum += 1;
    if (currentItemNum == 6) currentItemNum = 1;
    switchIndexBtn(currentItemNum);

    return currentItemNum;
}

function prevItem(currentItemNum) {
    slide(-1);
    currentItemNum -= 1;
    if (currentItemNum == 0) currentItemNum = 5;
    switchIndexBtn(currentItemNum);

    return currentItemNum;
}

//添加直接跳轉函數:
function slideTo(targetNum) {
    var itemWrapper=document.querySelector(".carousel-item-wrapper");
    itemWrapper.style.left=-(targetNum*carouselInfo.itemWidth)+'px';
    switchIndexBtn(targetNum);
}

function slide(slideItemNum) {
    var itemWrapper = document.querySelector(".carousel-item-wrapper");
    var currentLeftOffset = (itemWrapper.style.left) ? parseInt(itemWrapper.style.left) : 0,
        targetLeftOffset = currentLeftOffset - (slideItemNum * carouselInfo.itemWidth);

    itemWrapper.style.left = targetLeftOffset + 'px';
}

function switchIndexBtn(targetNum) {
    //切換當前的索引按鈕
    //刪除過去激活的.active-carousel-index-btn
    var activeBtn=document.querySelector(".active-carousel-index-btn");
    activeBtn.className=activeBtn.className.replace(" active-carousel-index-btn","");

    //添加新的激活索引按鈕
    var targetBtn=document.querySelectorAll(".carousel-index-btn")[targetNum-1];
    targetBtn.className+=" active-carousel-index-btn";
}

第2步的demo:carousel-step-2

3.實現自動播放功能:

function carouselControl() {
    //省略前面重複的代碼......

    for (var i = 0; i < indexBtns.length; i++) {
        //利用當即調用函數,解決閉包的反作用,傳入相應的index值
        (function (i) {
            indexBtns[i].onclick = function () {
                slideTo(i+1);
                currentItemNum=i+1;
            }
        })(i);
    }

    //添加定時器
    var scrollTimer;
    function play() {
        scrollTimer=setInterval(function () {
            currentItemNum=nextItem(currentItemNum);
        },2000);
    }
    play();

    function stop() {
        clearInterval(scrollTimer);
    }

    //綁定事件
    carouselWrapper.addEventListener('mouseover',stop);
    carouselWrapper.addEventListener('mouseout',play,false);

    /*DOM二級的addEventListener相對於on+sth的優勢是:
     * 1.addEventListener能夠前後添加多個事件,同時這些事件還不會相互覆蓋。
     * 2.addEventListener能夠控制事件觸發階段,經過第三個可選的useCapture參數選擇冒泡仍是捕獲。
     * 3.addEventListener對任何DOM元素都有效,而不只僅是HTML元素。*/
}

第3步的demo:carousel-step-3

4.關鍵點:實現循環播放功能:

function slide(slideItemNum) {
    var itemWrapper = document.querySelector(".carousel-item-wrapper");
    var currentLeftOffset = (itemWrapper.style.left) ? parseInt(itemWrapper.style.left) : 0,
        targetLeftOffset = currentLeftOffset - (slideItemNum * carouselInfo.itemWidth);

    /*不在這裏跳轉了。先處理偏移值,實現循環,再跳轉。*/
    //itemWrapper.style.left = targetLeftOffset + 'px';

    switch (true) {
            /*switch 的語法是:當case之中的表達式等於switch (val)的val時,執行後面的statement(語句)。*/
        case (targetLeftOffset>0):
            itemWrapper.style.transition="none";
            itemWrapper.style.left=-carouselInfo.trueItemNum*carouselInfo.itemWidth+'px';
            /*此處即至關於:itemWrapper.style.left='-2600px';*/
            targetLeftOffset=-(carouselInfo.trueItemNum-1)*carouselInfo.itemWidth;
            //至關於:targetLeftOffset=-2080;
            break;
        case (targetLeftOffset<-(carouselInfo.totalWidth-carouselInfo.itemWidth)):
            //此處即至關於:targetLeftOffset<-3120
            itemWrapper.style.transition="none";
            itemWrapper.style.left=-carouselInfo.itemWidth+'px';
            //至關於:itemWrapper.style.left='-520px';
            targetLeftOffset=-carouselInfo.itemWidth*2;
            //至關於:targetLeftOffset=-1040;
            break;
    }

    /*這裏我使用了setTimeout(fn,0)的hack
     * 參考bootstrap的carousel.js源碼,彷佛也利用了setTimeout(fn,0)這一hack。
     *
     * stackoverflow上有對這一hack的討論和解釋:
     * http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful
     * 根據第二個回答,我我的的理解是:setTimeout(fn,0)至關於異步執行內部的代碼fn,
     * 具體到這個輪播,就是在上一輪非過渡定位的頁面渲染工做(switch語句內部的case)結束以後,再執行setTimeout內部的過渡位移工做。
     * 從而避免了,非過渡的定位還未結束,就恢復了過渡屬性,使得這一次非過渡的定位也帶有過渡效果。
     **/

    //各位能夠試一試,把setTimeout內部的代碼放在外部,「循環」時會有什麼樣的錯誤效果。
    //itemWrapper.style.transition="left .2s ease-in";
    //itemWrapper.style.left=targetLeftOffset+'px';

    setTimeout(function () {
        itemWrapper.style.transition="left .2s ease-in";
        itemWrapper.style.left=targetLeftOffset+'px';
    },20);
}

第4步的demo:carousel-step-4

至此,就完成了一個完整的循環播放圖片輪播,欣賞一下本身的傑做吧~~~ヾ(✿゚▽゚)ノ

5、源碼及示例:

1.GitHub倉庫地址及完整代碼:JuniorTour/simple-standard-js-carousel

2.在線demo:原生JS循環播放圖片輪播組件 

很慚愧,只作了一點簡單的工做。若是以爲本文不錯的話,歡迎給個人GitHub點贊!

相關文章
相關標籤/搜索