前段時間學習了淘寶首頁的靜態頁面,其中收穫較大的的就是這個循環播放的圖片輪播組件,本文就將相關製做經驗分享給你們。javascript
先看看在線DEMO:原生JS循環播放圖片輪播組件 (支持IE8+,本文中的在線demo均未通過壓縮,能夠直接在瀏覽器中調試)css
以及GitHub倉庫地址及完整代碼:JuniorTour/simple-standard-js-carouselhtml
這個思路仍是很簡單的,經過觀察一些圖片輪播就能夠發現,圖片輪播通常是以一個尺寸較小的父元素做爲窗口,包裹住一組較長的長條狀的項目(item)子元素,再利用 overflow: hidden;
,將父元素做爲「窗口」,只顯示出的項目子元素的一部分,並經過改變項目子元素的定位或translate3d屬性,實現多張圖片項目動態播放。java
基本原理能夠參考這個demo:圖片輪播基本原理演示git
可是這樣簡單的輪播是不會循環播放的,也就是說當一輪圖片項目(item)播放到結尾;或者當在第一張圖(第一個項目)繼續向前時,就會超出內容子元素,出現空白部分,這通常不是咱們想要的結果。github
有多種思路能夠實現循環播放,我觀察到淘寶網首頁的圖片輪播是這樣的思路:bootstrap
複製開頭和結尾的項目,並分別放在開頭和結尾,當播放到開頭或結尾的項目,繼續播放,須要循環時,臨時取消transition屬性,並當即用定位跳轉至相應的真正的開頭或結尾以後,再恢復原來的transition,繼續正常滾動播放,從而利用視覺上的「欺騙」,實現帶有過渡效果的循環播放。瀏覽器
相應的原理能夠參考這個demo:圖片輪播循環原理演示閉包
核心理念是簡潔、語義化。這部分由於我學過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><</i> </button> <button id="next"> <!--next--> <i>></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>
總的來講比較簡單,重要的地方我加上了註釋,有存疑的地方,歡迎和我交流。
/*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; }
這一塊是主要部分,內容較多,所以咱們逐步來實現各部分功能以便於理解。
根據最開始的思路講解,咱們把這個輪播的JavaScript功能大體分爲如下4個部分:
1.左右滑動按鈕功能
2.索引按鈕跳轉功能
3.自動播放功能
4.循環播放功能。
咱們來分別逐步實現。
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
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
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
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
至此,就完成了一個完整的循環播放圖片輪播,欣賞一下本身的傑做吧~~~ヾ(✿゚▽゚)ノ
很慚愧,只作了一點簡單的工做。若是以爲本文不錯的話,歡迎給個人GitHub點贊!