輪播圖的實現原理並不難,但是步驟有些繁瑣。最近練習了一個輪播圖,大部分是跟着網上的教程寫的,然后自己做了一點兼容ie8的修改,加了點擊切換圖片的特效和手機端的滑動特效,讓這個輪播圖可以在響應式的網站中使用,同時兼容pc端和觸屏端。
輪播圖的樣式也分很多種,淡入淡出的輪播圖很容易實現,只需要把圖片全都疊在一起,讓相應的圖片輪流顯示就行了,但是滾動能的輪播圖就要復雜很多。這里介紹的是滾動的輪播圖:
原理:
實現的原理就是將所有的圖片橫向的排列起來,排成一個長方形,讓這個長方形的總體不斷平移,從而使圖片輪流顯示在div中,然后將div之外的隱藏起來就可以了
下面是詳細步驟:
第一步,基本骨架
1)將要輪播的所有的圖片都放到一個無序列表中
<div id="outer"> <ul id="imgList"> <li><img src="img/1.jpg"/></li> <li><img src="img/2.jpg"/></li> <li><img src="img/3.jpg"/></li> <li><img src="img/4.jpg"/></li> <li><img src="img/5.jpg"/></li> </ul> </div>
2)所有的li標簽左浮動,只要ul的寬度夠大,所有的圖片就會橫向的排列起來。
#imgList>li{ float: left; }
3)讓圖片的寬高等於div的寬高,這樣做不但能夠提高開發的效率,還更便於網站的維護,即使圖片源的尺寸都是不一樣的,也能正常顯示:
//getCompteredStyle方法和current方法作用相同,都是獲取dom對象的屬性,前者不兼容ie8,后者不兼容firefox,這樣寫可以解決兼容性的問題。
function getStyle(obj, name) { if(window.getComputedStyle) { return getComputedStyle(obj, null)[name]; } else { return obj.currentStyle[name]; } } //-- 獲取outer的寬度 var getOuterWidth = getStyle(outer,"width");
//去掉獲取到的寬度值的px,使成為number類型的變量,方便計算 var widthObject = getOuterWidth.match(/\d*/); var width = widthObject[0];
第二步,添加幾個導航點和兩個按鈕
<div id="navContainer">
<!--我們就是導航點 --> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> </div>
<!--我們是左右切換按鈕--> <a href="javascript:;" id="picLbtn"><img src="img/pic/picLbtn.png"></a> <a href="javascript:;" id="picRbtn"><img src="img/pic/picRbtn.png"></a>
1)這里的圓點使用a標簽來做,a標簽為內聯元素,是不能調整大小的。這里使用float:left給他設置為右浮動之后他就會轉化為塊級元素。當然,直接使用display:block也是可以的:
#navContainer>a{ z-index: 5; float: left; width: 15px; height: 15px; background-color: red; margin: 0 5px; opacity: 0.5; filter: alpha(opacity=50); border-radius: 100%; }
這里把a標簽的數量寫固定了,如果上面放置了十張圖片輪播,下面就要寫十個導航點。
可以根據圖片的數量動態的設置a標簽的數量,不過這樣寫意義不大:
獲取所有的imgList中所有的li標簽放在對象x中,
for(i=0,i<x.length-1){
x.appendchild(a)//每次循環向數組中添加一個
}
a.href=javascript:;
2)添加兩張圖片作為向左向右切換的按鈕,這種圖片很多網站都有,如果不會ps的話隨便找個帶輪播圖的網站占下來就可以用
3)導航點和輪播圖的位置要繼承div的位置,並且要動態的設置:
//(大div的寬度 - 導航區域的寬度)/ 2 = 導航區域的左邊距
navContainer.style.left = (outer.offsetWidth - navContainer.offsetWidth)/2 + "px";
//(大div的高度 - 按鈕區域的高度)/ 2 = 按鈕區域的上邊距
var tempBtnTop = (outer.offsetHeight - picLbtn.offsetHeight)/2 + "px"; picLbtn.style.top = tempBtnTop; picRbtn.style.top = tempBtnTop;
這樣,這樣不管大div改成多大,都不需要修改圖片和導航點的定位,這個思想后面會多次體現。
第三步,自動切換圖片
1)這里需要寫一個move函數,作用是設置ul移動的平移效果。調用該函數時需要傳遞四個參數:要執行動畫的對象,執行對象的屬性,執行對象的目標,速度,回調函數
function move(obj, attr, target, speed, callback){ //關閉上一個定時器 clearInterval(obj.timer); //獲取元素目前的位置 var current = parseInt(getStyle(obj, attr)); //判斷速度的正負值 //如果從0 向 800移動,則speed為正 //如果從800向0移動,則speed為負 if(current > target) { //此時速度應為負值 speed = -speed; } //開啟一個定時器,用來執行動畫效果 //向執行動畫的對象中添加一個timer屬性,用來保存它自己的定時器的標識 obj.timer = setInterval(function(){ //獲取box1的原來的left值 var oldValue = parseInt(getStyle(obj, attr)); //在舊值的基礎上增加 var newValue = oldValue + speed; //判斷newValue是否大於800 //從800 向 0移動 //向左移動時,需要判斷newValue是否小於target //向右移動時,需要判斷newValue是否大於target if((speed < 0 && newValue < target) || (speed > 0 && newValue > target)) { newValue = target; } //將新值設置給box1 obj.style[attr] = newValue + "px"; //當元素移動到0px時,使其停止執行動畫 if(newValue == target) { //達到目標,關閉定時器 clearInterval(obj.timer); //動畫執行完畢,調用回調函數 callback && callback(); } }, 30); }
2)自動輪播效果主要由autoChange函數實現,index變量再0到(imgArr.length - 1)之間不斷循環,就實現了圖片從一直循環展示
將偏移量設置為(-width*index),index函數每次都是累加的,這樣每次的偏移量都是width的值,也就是圖片的寬度
這段代碼方放在setInterval函數中,setInterval函數的時間參數就是輪播圖的輪播速度
function autoChange(){ //--這個定時器可以控制圖片停留的時間 //--每隔三秒執行一次,不斷的被執行圖片就不斷切換 timer = setInterval(function(){ index++; //--index對長度取余,就可以讓index一直在范圍內循環 index %= imgArr.length; //--這個move實現自動切換的平移效果 move(imgList , "left" , -width*index , 20 , function(){ //--執行move函數時調用set方法,將圖片顏色改變,並且首末過渡 setA(); setRed(); }); },3000); }
3)從最后一張圖片到第一張需要一個過渡效果,就是前面原理圖中的首尾相連
首先要在最后一張的圖片中再添加第一張圖片
<ul id="imgList"> <li><img src="img/pic/1.jpg"/></li> <li><img src="img/pic/2.jpg"/></li> <li><img src="img/pic/3.jpg"/></li> <li><img src="img/pic/4.jpg"/></li> <li><img src="img/pic/5.jpg"/></li> <li><img src="img/pic/1.jpg"/></li> </ul>
然后加一個判斷,當執行的最后一張時,讓他index立即變成0,當前顯示的圖片也會由最后一張變成第一張,而且因為最后一張和第一張時完全相同的,所以這個改變也是看不出來的,但是因為索引改變了,其他的也就跟着變成第一張了,再次自動循環的時候,就會自動變成第二張了。
function setA(){ //判斷當前索引是否是最后一張圖片 if(index >= imgArr.length - 1){ //--如果當前圖片是最后一張,將該圖片立即變為第一張 index = 0; imgList.style.left = 0; } }
4)改變下面導航點的顏色,循環到哪個圖片,對應的導航點就會變成黑色,其他的則為紅色:
function setRed(){ for(var i=0 ; i<allA.length ; i++){ //--去掉內聯樣式里的顏色,顯示樣式表里的紅色 //--如果這里不這么寫的話,他就會每次遍歷到某個圖片,就會把他的導航點變成黑色,不會變回來 //--這么寫每次調用都會先把大家都變成紅色,再把需要的變成黑色 allA[i].style.backgroundColor = ""; } allA[index].style.backgroundColor = "black"; }
第四步,點擊導航點切換圖片
1)寫一個for循環,0到長度減1賦值給allA對象的num屬性,這樣每個導航點都對應着一個下標:
2)當點擊導航點時,觸發事件,將index設置為當前的num值,在執行move函數,他的偏移量就是改變后的-width * index,圖片就會跳轉到當前導航點對應的圖片,具體代碼如下:
var allA = document.querySelectorAll("#navContainer>a");
for(var i=0; i<allA.length ; i++){ //為每一個超鏈接都添加一個num屬性 allA[i].num = i; allA[i].onclick = function(){ //--在點擊時,要關閉自動切換的定時器 clearInterval(timer); //--獲取點擊超鏈接的索引,並將其設置為index index = this.num; //--setA只控制導航點時的效果 setRed(); //這個move是實現點擊切換的平移效果,每次調用只會執行一次 //--四個參數:要執行動畫的對象,執行對象的屬性,執行對象的目標,速度,回調函數 move(imgList , "left" , -width*index , 20 , function(){ //開啟被上面clearInterval關閉的自動切換程序 autoChange(); }); }
在執行單擊觸發函數時要用clearInterval把自動切換圖片的定時器timer關掉,不然單擊切換圖片和自動切換圖片同時執行網頁會非常混亂。
同時也要調用setA函數,要將當前點擊的圖片設置為導航點變為黑色。
第五步,點擊左右按鈕切換圖片
1)關閉timer定時器
2)左邊的點擊按鈕是顯示當前圖片左邊的圖片,右邊的點擊按鈕是顯示當前圖片右邊的圖片。因此要進行如下判斷:
如果當前是第一張:左邊的點擊按鈕不工作
如果是當前是最后一張:右邊的點擊按鈕不工作(為了方便理解,和代碼中的是反着寫的)
3)完成判斷后,調用setA函數設置導航點的顏色,然后開啟正常的循環:
picLbtn.onclick = function(){ clearInterval(timer); if (index != 0){ index = index-1; } setRed(); move(imgList , "left" , -width*index , 20 , function(){ autoChange(); }); }
picRbtn.onclick = function(){ clearInterval(timer); if (index != allA.length - 1) { index = index+1; } setRed(); move(imgList , "left" , -width*index , 20 , function(){ autoChange(); }); } }
第六步,滑動屏幕切換圖片
這個滑動效果只有在手機端電腦端或者打開瀏覽器的toggle device toobar模式才能用,光用鼠標拖拽是沒有用的!!
1)定義4個變量,用來存放一些數值:
var startX = 0; //記錄起始 剛剛觸摸的點的位置 x的坐標 var moveX = 0; //滑動的時候x的位置 var distanceX = 0; //滑動的距離 var isMove = false;//是否滑動了
2)定義手指觸摸屏幕觸發函數:
在手指觸摸到屏幕時,將觸摸點的橫坐標保存到startX變量中:
outer.addEventListener('touchstart', function(e){ clearInterval(timer); startX = e.touches[0].clientX; //--觸摸點的橫坐標 });
3)定義手指滑動觸發函數:
touchmove事件是手指觸摸在屏幕上就一直觸發的,因此distanceX函數最終保存的值將是手指離開屏幕時的值,用這個值減去startX就是滑動的距離:
這里傳遞給move函數的偏移量值是(-width*index + distanceX),因此ul偏移的寬度是distanceX,就實現了圖片跟隨手指滑動的效果:
outer.addEventListener('touchmove',function(e){e moveX = e.touches[0].clientX;//--獲取手指當前接觸屏幕位置的橫坐標 distanceX = moveX - startX; //--移動的距離=現在-初始 isMove = true; move(imgList , "left" , -width*index+distanceX , 20 , function(){}); });
4)定義手指離開屏幕觸發函數
用div的寬度除以3(記作x),比較用剛才獲得的滑動的距離(記作y)。
如果y > x,認為滑動有效
如果往左滑就是切右邊的圖
如果往右滑就是切左邊的圖
如果y < x,認為滑動的距離太短了,滑動無效,吸附回去
當前如果是第一張,還往右滑,或者是最后一張還能往左滑,同樣認為滑動無效,吸附回去
outer.addEventListener('touchend', function(){ //--滑動超過 1/3才可以切換圖片,否則即為無效,則吸附回去 //math.abs取絕對值 if(isMove && Math.abs(distanceX) > width/3){ if(distanceX > 0 && index != 0){ //上一張 index = index - 1; } else if(distanceX < 0 && index != imgArr.length - 2){ //下一張 index = index + 1; } move(imgList , "left" , -width*index, 20 , function(){}); } else if(isMove && Math.abs(distanceX) < width/3){ move(imgList , "left" , -width*index, 20 , function(){}); } setRed(); autoChange(); });
最后整理之后的代碼就是這樣:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style type="text/css"> *{ margin: 0; padding: 0; } #outer{ width: 500px; height: 333px; margin: 50px auto; position: relative; overflow: hidden; } #imgList{ list-style: none; position: absolute; left: 0px; } #imgList>li{ float: left; } #imgList>li>img{ width: 500px; height: 333px; } #navContainer{ position: absolute; bottom: 15px; } #navContainer>a{ z-index: 5; float: left; width: 15px; height: 15px; background-color: red; margin: 0 5px; opacity: 0.5; filter: alpha(opacity=50); border-radius: 100%; } #navContainer>a:hover{ background-color: black; } #picLbtn, #picRbtn{ position: absolute; } #picLbtn{ left: 8px; } #picRbtn{ right: 8px; } </style> <script type="text/javascript"> window.onload = function(){ var imgList = document.getElementById("imgList"); var navContainer = document.getElementById("navContainer"); var outer = document.getElementById("outer"); var picLbtn = document.getElementById("picLbtn"); var picRbtn = document.getElementById("picRbtn"); var imgArr = document.querySelectorAll("#imgList>li>img"); /*設置按鈕居中*/ navContainer.style.left = (outer.offsetWidth - navContainer.offsetWidth)/2 + "px"; var tempBtnTop = (outer.offsetHeight - picLbtn.offsetHeight)/2 + "px"; picLbtn.style.top = tempBtnTop; picRbtn.style.top = tempBtnTop; //-- 獲取元素樣式,最低兼容ie8 function getStyle(obj, name) { if(window.getComputedStyle) { return getComputedStyle(obj, null)[name]; } else { return obj.currentStyle[name]; } } //-- 獲取outer的寬度 var getOuterWidth = getStyle(outer,"width"); var widthObject = getOuterWidth.match(/\d*/); var width = widthObject[0]; //-- 根據圖片的數量設置ul的總寬度 imgList.style.width = width*imgArr.length+"px"; function move(obj, attr, target, speed, callback){ clearInterval(obj.timer); var current = parseInt(getStyle(obj, attr)); if(current > target) { speed = -speed; } obj.timer = setInterval(function(){ var oldValue = parseInt(getStyle(obj, attr)); var newValue = oldValue + speed; if((speed < 0 && newValue < target) || (speed > 0 && newValue > target)) { newValue = target; } obj.style[attr] = newValue + "px"; if(newValue == target) { clearInterval(obj.timer); callback && callback(); } }, 30); } //設置默認選中的效果 var index = 0; var allA = document.querySelectorAll("#navContainer>a"); allA[index].style.backgroundColor = "black"; //-- 正常開啟自動切換函數 autoChange(); function setA(){ if(index >= imgArr.length - 1){ index = 0; imgList.style.left = 0; } } function setRed(){ for(var i=0 ; i<allA.length ; i++){ allA[i].style.backgroundColor = ""; } allA[index].style.backgroundColor = "black"; } var timer; //--自動切換圖片 function autoChange(){ timer = setInterval(function(){ index++; index %= imgArr.length; move(imgList , "left" , -width*index , 20 , function(){ setA(); setRed(); }); },3000); } //--實現點擊導航點切換圖片 //--調用setA、move、autochange函數 for(var i=0; i<allA.length ; i++){ allA[i].num = i; allA[i].onclick = function(){ clearInterval(timer); index = this.num; setRed(); move(imgList , "left" , -width*index , 20 , function(){ autoChange(); }); } picLbtn.onclick = function(){ clearInterval(timer); if (index != 0){ index = index-1; } setRed(); move(imgList , "left" , -width*index , 20 , function(){ autoChange(); }); } picRbtn.onclick = function(){ clearInterval(timer); if (index != allA.length - 1) { index = index+1; } setRed(); move(imgList , "left" , -width*index , 20 , function(){ autoChange(); }); } } var startX = 0; var moveX = 0; var distanceX = 0; var isMove = false; outer.addEventListener('touchstart', function(e){ clearInterval(timer); //--清除定時器,要記得事件結束之后再打開 startX = e.touches[0].clientX; //--觸摸點的橫坐標 }); outer.addEventListener('touchmove',function(e){ moveX = e.touches[0].clientX;//--獲取當前手的橫坐標 distanceX = moveX - startX; //--移動的距離=現在-初始 isMove = true;//證明滑動過 move(imgList , "left" , -width*index+distanceX , 20 , function(){}); }); outer.addEventListener('touchend', function(){ if(isMove && Math.abs(distanceX) > width/3){ if(distanceX > 0 && index != 0){ index = index - 1; } else if(distanceX < 0 && index != imgArr.length - 2){ index = index + 1; } move(imgList , "left" , -width*index, 20 , function(){}); } else if(isMove && Math.abs(distanceX) < width/3){ move(imgList , "left" , -width*index, 20 , function(){}); } setRed(); autoChange(); }); } </script> </head> <body> <div id="outer"> <ul id="imgList"> <li><img src="img/pic/1.jpg"/></li> <li><img src="img/pic/2.jpg"/></li> <li><img src="img/pic/3.jpg"/></li> <li><img src="img/pic/4.jpg"/></li> <li><img src="img/pic/5.jpg"/></li> <li><img src="img/pic/1.jpg"/></li> </ul> <div id="navContainer"> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> </div> <a href="javascript:;" id="picLbtn"><img src="img/pic/picLbtn.png"></a> <a href="javascript:;" id="picRbtn"><img src="img/pic/picRbtn.png"></a> </div> </body> </html>
缺點和解決(這個一定要看!!!):
缺點:切換到手機端的時候:點擊導航點和左右切換按鈕會出現問題
原因:這個是因為系統把點擊導航點和按鈕也當成了滑動事件,這樣就要同時相應兩組事件,所以會出問題(俺是這么理解的,對不對也不知道)
解決:解決就很簡單了,反正手機端也不需要點擊導航點和切換按鈕(沒有人會在手機上用那玩意)
所以如果是用在響應式布局的話,判斷一下屏幕寬度,小於660px:左右按鈕display:none,然后把導航點事件移除,大於660px就什么都不做
如果用在純手機端,就直接把導航點的事件和左右按鈕代碼刪掉就行了
這個程序寫的還是有點模塊化思想的,四五六步的內容隨便刪掉哪個其他互不影響。
結尾:
最后奉上B站視頻教程的鏈接https://www.bilibili.com/video/av34087791,上文的大部分內容都來自這組視頻~