原生JS實現輪播圖--第二章動畫實現

安安靜靜發表於2018-09-05

最近看了一組故宮的圖片,覺得很大氣,決定選用故宮的圖片用來展示,如下:

原生JS實現輪播圖--第二章動畫實現

樣式構成與上一節相似,這裡稍做調整,便不再贅述。

輪播圖的動畫效果,就是圖片按照規定的方向,不停滾動以展示每一張圖片,同時還可以通過點選以改變原本的展示順序。實現有一下幾點:

  1. 不做點選操作,圖片自行滾動(這裡我們規定自右向左滾動);
  2. 點選左右箭頭,實現圖片翻頁;
  3. 點選提示圓點,顯示不同圖片;
  4. 功能整合。

自動迴圈滾動

圖片自行滾動務必就要使用到定時器setInterval或者setTimeout,使得移動函式每隔一段時間執行一次,以達到迴圈滾動的動畫效果。

定時器的介紹

  • setInterval()方法會重複呼叫一個函式或執行一個程式碼段,在每次呼叫之間具有固定的時間延遲,返回一個intervalID,可以作為引數傳給clearInterval()來取消該定時器。

  • setTimeout()方法設定一個定時器,在定時器到期後會執行一個函式或一段程式碼,返回值timeoutID是一個正整數,表示定時器的編號。這個值可以傳遞給clearTimeout()來取消該定時器。

setInterval( )

let intervalID = window.setInterval(func, delay);
複製程式碼
  • intervalID 是此重複操作的唯一辨識符,可以作為引數傳給clearInterval()
  • func 是你想要重複呼叫的函式。

setTimeout( )

var timeoutID = scope.setTimeout(function, delay); 
複製程式碼
  • function是你想要在delay毫秒之後執行的函式。
  • 延遲的毫秒數 (一秒等於1000毫秒),函式的呼叫會在該延遲之後發生。

例項研究

首先,理解圖片的迴圈滾動,實際上就是多次連續的移動,每次移動固定的距離,若干次的移動,就形成了迴圈效果,那麼此處我們應該首個實現的是移動一次的效果,即移動函式--movefunc

我們先回顧一下各個容器之間的層級關係:

<!--最外層的父級容器-->
    <div class="imgScroll">
        <!--圖片展示區-->
        <div class="showContainer">
            <div class="container" style="left: 0px;">
                <img src="https://dimg02.c-ctrip.com/images/100b11000000qezw729A4_R_1600_10000_Mtg_7.jpg" alt="A cat">
                <img src="https://dimg05.c-ctrip.com/images/100u0x000000le38uA71D_R_1600_10000_Mtg_7.jpg" alt="A dog">
                <img src="https://dimg08.c-ctrip.com/images/100811000000qrlfxA3E0_R_1600_10000_Mtg_7.jpg" alt="dandelion">
            </div>
        </div>
        <!--底部提示區-->
        <div class="dots">
            <span class="dot active"></span>
            <span class="dot"></span>
            <span class="dot"></span>
        </div>
        <!--左右箭頭-->
        <div class="left-triangle triangle">
            <span>&lt;</span>
        </div>
        <div class="right-triangle triangle">
            <span>&gt;</span>
        </div>
    </div>

複製程式碼

最外層div.imgScroll的是整個實現的父容器,其子元素div.showContainer是展示容器,裡層div.ontainer是滾動容器,該滾動容器相對於展示容器是絕對定位,那麼移動函式就是要讓滾動容器進行移動,即要改變滾動容器的style.left屬性。

移動函式--movefunc( )

上面我們規定,圖片要自右向左滾動,即滾動容器每次向左移動一張圖片的寬度(記變數為oneImgWidth)即div.container.style.left每次減小一個oneImgWidth

首先要獲取滾動容器,和它的的初始狀態。原生方法:getElementsByClassName()getElementById()都可以獲取DOM文件中的元素,在html結構中沒有使用id進行元素表示,則使用前者,但是要注意該方法獲取得到的是一個元素集合HTMLCollection,須通過下標的方式獲取第一個元素:

var container = document.getElementsByClassName("container")[0];    //獲取滾動容器
var oldLeftPos = parseInt(container.style.left);    //獲取滾動容器的初始狀態,並利用parseInt()方法取整
複製程式碼

注意: 在上面的html結構中,你會注意到有一句行內樣式style="left: 0px;",這是為了防止style.left取不到值而報錯。

然後獲取圖片的寬度,即為滾動容器每次移動的步長:

var oneimgWidth = container.children[0].offsetWidth;    //這是一個整數
複製程式碼

滾動容器左移:

var newLeftPos = oldLeftPos - oneimgWidth;
container.style.left = newLeftPos + "px";   //記得加上單位px
複製程式碼

這樣就實現了一次移動效果--移動函式

  function movefunc() {
    var container = document.getElementsByClassName("container")[0];    //獲取滾動容器
    var oneImgWidth = container.children[0].offsetWidth;    //獲取一張圖片的寬度,即每次左移的距離
    var oldLeft = parseInt(container.style.left);    //獲取滾動容器的初始的左距離,利用parseInt()方法取整
    var newLeft = oldLeft - oneImgWidth;
    container.style.left = newLeft + "px";      //改變滾動容器的左距離
  }
複製程式碼

自動滾動函式--rollAuto( )

實現了移動函式,我們開始正真的輪播圖實現的第一步動畫效果——自動迴圈滾動,前面已經介紹過定時器的基本使用,現在開始利用定時器和移動函式,實現迴圈移動形成動畫:

建立一個rollAuto()定時器函式:

function rullAuto() {
  setInterval("movefunc()", 800);   //每隔800毫秒執行一次移動函式
}
rullAuto();     //需要先執行一次定時器函式,才可以開始定時器
複製程式碼

在輪播圖中不進行任何操作時,圖片會自行滾動,但是當滑鼠移動到展示區中時,滾動的圖片則停止滾動,滑鼠再移出展示區,則滾動繼續,那麼在這一進一出的兩次操作中,就涉及了兩次對於定時器的不同操作,一次清除,一次恢復,其中,移進為清除,移出為恢復

根據上述定時器的介紹,清除定時器需要一個表示需要被清除定時器的編號,我們使用一個變數timer來儲存該定時器。

var timer = setInterval("movefunc()", 800);   //每隔800毫秒執行一次移動函式
clearInterval(timer);   //清除使移動函式迴圈執行的定時器
複製程式碼

滑鼠的移動會觸發滑鼠事件事件,例如此處,滑鼠移進會觸發onmouseover事件,滑鼠移出會觸發onmouseout事件,則我們可以為展示區元素繫結這兩個事件:

var showContainer = document.getElementsByClassName("showContainer")[0];     //獲取展示區元素
//繫結滑鼠事件
showContainer.onmouseover = function() {
    clearInterval(timer);
};
showContainer.onmouseout = function() {
    rullAuto();   //滑鼠移出,繼續執行移動函式
};
複製程式碼

最後我們將這個自行滾動函式整理一下:

function autoRullImg() {
    var showContainer = document.getElementsByClassName("showContainer")[0]; 
    var timer = null;
    function rullAuto() {
        timer =  setInterval("movefunc()", 800); 
    }
    rullAuto();
    showContainer.onmouseover = function() {
        clearInterval(timer);
    };
    showContainer.onmouseout = function() {
        rullAuto();
    };
}
複製程式碼

這樣作為一個完整的函式就拿過了使用,一定會讓你大失所望,圖片滾著滾著就不見了,containerstyle.left還在不斷減小。你會發現這不是我們想要的結果。我們希望的是,當最後一張圖片滾出展示區後,應該出現第一張圖,那麼此處就需要做一個極限條件的判斷。

var container = document.getElementsByClassName("container")[0];    //獲取滾動容器
var imgNum = container.children.length;     //獲取圖片數量
var oneImgWidth = container.children[0].offsetWidth;    //獲取每張圖的寬度(CSS樣式設定中,每張圖寬頻一樣)
//如果最後一張圖移出展示區,初始化滾動容器的.style.left屬性值
if (parseInt(container.style.left) <= -imgNum * oneImgWidth) {
    container.style.left = "0px";
}
複製程式碼

最終整合版如下:

function autoRullImg() {
    var timer = null;
    var showContainer = document.getElementsByClassName("showContainer")[0]; 
    var container = document.getElementsByClassName("container")[0];  
    var imgNum = container.children.length;  
    var oneImgWidth = container.children[0].offsetWidth;  

    function rullAuto() {
        timer =  setInterval(function(){
            movefunc();
            if (parseInt(container.style.left) <= -imgNum * oneImgWidth) {
                container.style.left = "0px";
            }
        }, 800); 
    }
    rullAuto();
    
    showContainer.onmouseover = function() {
        clearInterval(timer);
    };
    showContainer.onmouseout = function() {
        rullAuto();
    };
}
複製程式碼

點選左右箭頭,實現圖片翻頁

點選向左箭頭,滾動容器應該向右移動;點選向右箭頭,滾動容器向左移動,這裡就出現了一個問題,我們的移動函式,是將container.style.left減少一個圖片的寬度,即使得圖片向左移動,自行滾動函式和點選向右箭頭效果與此相同。

但是點選向左箭頭,則是朝著方向了,因此,我們之前的移動函式movefunc就需要改動了,為了區分兩個函式,我們現將改進的移動函式更名為movement(),不再僅僅是移動圖片了,若是其他元素的移動也就可以重用該函式了。

可重用移動函式--movement( )

為了滿足兩個方向的移動,我們就可以把移動的步長作為引數,傳給該函式,函式就可以根據引數的正負值,進行不同方向的移動,如下:

  function movement(offset) {
    var container = document.getElementsByClassName("container")[0]; 
    var oldLeft = parseInt(container.style.left); 
    var newLeft = oldLeft + offset;
    container.style.left = newLeft + "px";
  }
複製程式碼

這樣就完成了一個可重用的移動函式,移動步長可以通過引數控制。在此可以得出,需要滾動容器向左移動時,就傳進一個負值,反之則為正值。

那麼,autoRullImg()函式中就要把原本的移動函式替換為新的移動函式,並傳一個負值進去:

movement(-oneImgWidth);
複製程式碼

給左右箭頭繫結點選事件:

同樣在此之前,需要先獲取元素:

var leftTriangle = document.getElementsByClassName("left-triangle")[0];         //左箭頭
var rightTriangle = document.getElementsByClassName("right-triangle")[0];       //右箭頭
複製程式碼

在這裡點選向右箭頭,container向左移動;點選向左箭頭,container向右移動,給左右箭頭繫結點選事件:

leftTriangle.onclick = function() {
    movement(oneImgWidth);
};
rightTriangle.onclick = function() {
    movement(-oneImgWidth);
};
複製程式碼

同樣,在擊左右箭頭控制圖片移動的時候,也需要進行極限條件的判斷,在點選向右箭頭的情況中,如果已經是第一張圖再點選向右箭頭,應該出現的事最後一張圖;再點選向左箭頭時,如果已經到了最後一張圖,再點選向右箭頭,應該出現的是第一張圖。

function clickTriangle() {
    var container = document.getElementsByClassName("container")[0]; 
    var imgNum = container.children.length;  
    var oneImgWidth = container.children[0].offsetWidth;
    leftTriangle.onclick = function() {
        if (parseInt(container.style.left) >= 0) {
            container.style.left = -(imgNum - 1) * oneImgWidth + "px";
        } else {
            movement(oneImgWidth);     
        }
    };
    rightTriangle.onclick = function() {
        if (parseInt(container.style.left) <= -(imgNum - 1) * oneImgWidth) {
            container.style.left = "0px";
        } else {
            movement(-oneImgWidth);
        }
    };
}
複製程式碼

點選提示圓點,顯示不同圖片

給各提示圓點繫結點選事件:

function clickDots(){
    var container = document.getElementsByClassName("container")[0];  
    var oneImgWidth = container.children[0].offsetWidth;
    var dots = document.getElementsByClassName("dots")[0].children;     //獲取提示圓點
    for(var i = 0; i < dots.length; i++){
        (function(n){
            dots[n].onclick = function(){
                container.style.left = -n*oneImgWidth + "px";
            }
        })(i);
    }
}
複製程式碼

因為迴圈體中使用了迴圈變數i,需要使用建立匿名函式並立即執行的方式,解決閉包問題。

到此為止,已經實現基本需求,圖片自行滾動、滑鼠移動到展示區滾動停止、滑鼠移出展示區滾動繼續、點選向左箭頭出現當前展示圖的左邊的圖、點選向右箭頭出現當前展示的圖片的右邊的圖片、點選底部提示圓點出現相應圖片。

但是還有一個問題,每次圖片滾動,和點選左右箭頭,底部的提示圓點並沒有相應高亮顯示。應該達到的效果是第一張圖顯示時,一個提示圓點應該高亮;第二張圖顯示時,第二個提示圓點要高亮...最後一張圖顯示時,最後一個提示圓點高亮。

因此應該有個記號,用來標記每一次展的示圖片。因此我們宣告這樣一個記號叫做index,用來標識當前展示的圖片,其初始狀態為0,即展示區顯示第一張圖。

記號的變化分析:

滾動容器div.container每向左滾動一次,index就應增加1,而當index增加到為imgNum時,即最後一張圖恰好左移出展示區時,應該展示第一張圖片,即index要重置為0;滾動容器div.container向右滾動時,則與此相反。

不論是自行滾動函式,還是點選事件函式,都會引起記號index的變化,所以我們需要將index的宣告和初始化,放在這些所有函式的最外層,極限條件的判斷放進每一個動畫函式autoRullImg()clickTriangle()clickDots()裡面:

//點選向右箭頭
rightTriangle.onclick = function() {
    if (parseInt(container.style.left) <= -(imgNum - 1) * oneImgWidth) {
        container.style.left = "0px";
    } else {
        movement(-oneImgWidth);
    }
    index = index + 1;
    if (index > imgNum - 1) {
        index = 0;
    }
};

//點選向左箭頭
leftTriangle.onclick = function() {
    if (parseInt(container.style.left) >= 0) {
        container.style.left = -(imgNum - 1) * oneImgWidth + "px";
    } else {
        movement(oneImgWidth);     
    }
    index = index - 1;
    if (index < 0) {
        index = imgNum - 1;
    }
};
複製程式碼

自行滾動函式修改如下:

function autoRullImg() {
    var timer = null;
    var showContainer = document.getElementsByClassName("showContainer")[0]; 
    var container = document.getElementsByClassName("container")[0];  
    var imgNum = container.children.length;  
    var oneImgWidth = container.children[0].offsetWidth;  

    function rullAuto() {
        timer =  setInterval(function(){
            index++;
            if (index > imgNum - 1) {
                index = 0;
            }
            movement(-oneImgWidth);
            if (parseInt(container.style.left) <= -imgNum * oneImgWidth) {
                container.style.left = "0px";
            }
        }, 800); 
        if (parseInt(container.style.left) <= -imgNum * oneImgWidth) {
            container.style.left = "0px";
        }
    }
    rullAuto();
    
    showContainer.onmouseover = function() {
        clearInterval(timer);
    };
    showContainer.onmouseout = function() {
        rullAuto();
    };
}
複製程式碼

高亮函式--showDots( )

有了記號,則需要一個高亮顯示的函式,根據記號index來讓底部提示圓點高亮顯示,我們先獲取包含提示圓點的圓點容器,然後獲取他的子元素,即<span class="dot"></span>小圓點,遍歷這些元素,對比它們的下標,如果和記號index相同,則改變該提示圓點為高亮,即新增一個active類。

    function showDots(target) {
        var dots = document.getElementsByClassName("dots")[0].children;
        for (var i = 0; i < dots.length; i++) {
            if (i !== target) {
                dots[i].classList.remove("active");       //增加表示高亮的類名
            } else {
                dots[i].classList.add("active");      //去掉高亮
            }
        }
    }
複製程式碼

注意: 由於點選底部提示圓點,會使得記號index有跳動,即不是index的變化不在1之內,不僅僅是+1-1的變化,這一點需要尤其注意。自行滾動函式是無限迴圈執行的,當點選過底部提示圓點後,記號值倍打亂,所以要將index值返回出去,以便自行滾動函式可以獲取:

    function clickDots(){
        for(var i = 0; i < dots.length; i++){
            (function(n){
                dots[n].onclick = function(){
                    container.style.left = -n*oneImgWidth + "px";
                    index = n;
                    showDots(index);
                }
            })(i);
        }
        return index;       //返回記號值
    }
複製程式碼

功能整合

現在終於大功告成了,每一部分的功能都已經實現,現在就只需要將所有的函式整合了,別掉以輕心,整合函式依然是一個不可忽視的步驟,這其中你可能會發現一些問題,這些問題將大大降低使用者的體驗。那麼現在來開始整合函式。

為了避免出現全域性變數,我們將所有的函式和變數全部放進一個函式中,宣告此函式名為slideShow(),為實現HTML文件載入完就開始執行這個函式,需要使用window.onload()方法:

window.onload = slideShow;
複製程式碼

我們在多個函式中都重複獲取了DOM元素,現在我們把獲取元素放在slideShow()這個函式的首部:


function autoRullImg() {
    var timer = null; 
    var showContainer = document.getElementsByClassName("showContainer")[0]; 
    var container = document.getElementsByClassName("container")[0];  
    var imgNum = container.children.length;  
    var oneImgWidth = container.children[0].offsetWidth;  
    var leftTriangle = document.getElementsByClassName("left-triangle")[0];
    var rightTriangle = document.getElementsByClassName("right-triangle")[0];
    var dots = document.getElementsByClassName("dots")[0].children;     //獲取提示圓點
    var index = 0;
    
    function showDots(target) {
        var dots = document.getElementsByClassName("dots")[0].children;
        for (var i = 0; i < dots.length; i++) {
            if (i !== target) {
                dots[i].classList.remove("active");       //增加表示高亮的類名
            } else {
                dots[i].classList.add("active");      //去掉高亮
            }
        }
    }

    function movement(offset) {
        var oldLeft = parseInt(container.style.left); 
        var newLeft = oldLeft + offset;
        container.style.left = newLeft + "px";
      }

    
    showContainer.onmouseover = function() {
        clearInterval(timer);
    };
    showContainer.onmouseout = function() {
        rullAuto();
    };

    function clickTriangle() {
        leftTriangle.onclick = function() {
            if (parseInt(container.style.left) >= 0) {
                container.style.left = -(imgNum - 1) * oneImgWidth + "px";
            } else {
                movement(oneImgWidth);     
            }
            index = index - 1;
            if( index < 0){ index = imgNum - 1;}
            showDots(index);
        };
        rightTriangle.onclick = function() {
            if (parseInt(container.style.left) <= -(imgNum - 1) * oneImgWidth) {
                container.style.left = "0px";
            } else {
                movement(-oneImgWidth);
            }
            index = index + 1;
            if( index > imgNum - 1){ index = 0;}
            showDots(index);
        };
    }
    clickTriangle() 

    function clickDots(){
        for(var i = 0; i < dots.length; i++){
            (function(n){
                dots[n].onclick = function(){
                    container.style.left = -n*oneImgWidth + "px";
                    index = n;
                    showDots(index);
                }
            })(i);
        }
        return index;
    }
    clickDots()

    function rullAuto() {
        timer =  setInterval(function(){
            movement(-oneImgWidth);
            index = index + 1;
            if( index > imgNum - 1){ index = 0;}
            showDots(index);
            if (parseInt(container.style.left) <= -imgNum * oneImgWidth) {
                container.style.left = "0px";
            }
        }, 2000); 
    }
    rullAuto();
}
window.onload = autoRullImg;
複製程式碼

HTML程式碼如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>img scroll</title>
    <link rel="stylesheet" href=".layout.css">
    <script src="./scrollImg.js"></script>
</head>

<body>
    <div class="imgScroll">
        <div class="showContainer">
            <div class="container">
                <img src="https://dimg02.c-ctrip.com/images/100b11000000qezw729A4_R_1600_10000_Mtg_7.jpg" alt="A cat">
                <img src="https://dimg05.c-ctrip.com/images/100u0x000000le38uA71D_R_1600_10000_Mtg_7.jpg" alt="A dog">
                <img src="https://dimg08.c-ctrip.com/images/100811000000qrlfxA3E0_R_1600_10000_Mtg_7.jpg" alt="dandelion">
            </div>
        </div>
        <div class="dots">
            <span class="dot active"></span>
            <span class="dot"></span>
            <span class="dot"></span>
        </div>
        <div class="left-triangle triangle">
            <span>&lt;</span>
        </div>
        <div class="right-triangle triangle">
            <span>&gt;</span>
        </div>
    </div>

</body>
</html>

複製程式碼

CSS程式碼如下:

.imgScroll {
  width: 800px;
  margin: 0 auto;
  position: relative;
}
.imgScroll .showContainer {
  width: 800px;
  height: 533px;
  margin: 0 auto;
  overflow: hidden;
  position: relative;
}
.imgScroll .showContainer .container {
  width: 9999px;
  position: absolute;
  left: 0px;
}
.imgScroll .showContainer .container img {
  display: block;
  float: left;
  width: 800px;
}
.imgScroll .triangle {
  position: absolute;
  top: 40%;
  cursor: pointer;
  font-size: 40px;
  color: lightgray;
}
.imgScroll .triangle:hover {
  color: gray;
}
.imgScroll .triangle.left-triangle {
  left: -50px;
}
.imgScroll .triangle.right-triangle {
  right: -50px;
}
.imgScroll .dots {
  width: 100%;
  text-align: center;
  height: 50px;
  line-height: 50px;
  cursor: pointer;
}
.imgScroll .dots .dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: lightgray;
  margin-right: 10px;
}
.imgScroll .dots .dot.active {
  background-color: gray;
}

複製程式碼

相關文章