
是不是有點印象了,沒錯,他的最基本的用法就是左右滑動,插件使用者只需要寫幾行簡單的html和js即可實現一個簡單滑動效果,不過你完全可以組合各種元素來適應不同的場景。
當然插件我已經寫好了,咱先看下這個插件是怎么來用的,對插件有一個大概了解,一會寫起來不至於太懵逼。。。
插件地址:https://github.com/laravuel/swiper.git
demo目錄有演示和用法,不過插件我用了webpack和babel轉碼,可以不用管,直接看src/swiper.js即可。
<!-- demo.html --> <!-- swiper名稱可以自定義的啦 --> <div id="swiper"> <!-- swiper-item名稱也可以自定義啦,相當於一個滑塊 --> <div class="swiper-item"> <img src="./images/1.jpg" /> </div> <div class="swiper-item"> <img src="./images/2.jpg" /> </div> <div class="swiper-item"> <img src="./images/3.jpg" /> </div> </div> <script src="../dist/swiper.js"></script> <script> new Swiper({ swiper: '#swiper', // swiper節點名稱 item: '.swiper-item', // swiper內部滑塊的節點名稱 autoplay: false, // 是否自動滑動 duration: 3000, // 自動滑動間隔時間 change(index) { // 每滑動一個滑塊,插件就會觸發change函數,index表示當前的滑塊下標 console.log(index); } }); </script>
就是這么簡單,插件本身只是一個類,你只需要new一個對象出來,然后傳遞一些參數就ok了。而且,插件還提供了一個change方法,讓使用者可以在外部控制滑塊的滑動!
const swiper = new Swiper({...}); swiper.change(2); // 滑動到第三個滑塊
那么接下來,就是我們的教程時間了,我也不確定你能不能硬着頭皮看完,不過我敢肯定,如果你能夠親手把插件寫出來,你肯定會開心的飛起!!!
由於本次教程內容比較多,所以我分上下兩部分來講,第一部分主要講解原理,第二部分開始着手編寫插件。所以,感興趣的小伙伴可以加個關注先。
1. 功能分析
俗話說,一上來就貼代碼純屬耍流氓~
我們要清楚自己想實現哪些功能,懶得思考的童鞋可以結合我上面的動圖來分析:
- 滑塊可以左右滑動(支持移動端和pc端)
- 滑塊塊內部可以寫任何元素
- 滑動到第一個和最后一個滑塊時會有一個限制,防止越界
- 能夠自動播放
我們所能看到的大概就這些,接下來我們會對這些功能一一進行拆解和分析。
2. 實現原理
上面簡單梳理了一些功能,其實可以再擴展出以下幾個問題:
- 滑塊的html結構是什么樣的?
- 滑塊的滑動原理是什么?
- 如何來觸發滑動?
別急,一個個來
2.1.1 滑塊的html結構是什么樣的?
我們先來看一張圖:

這就是一個滑塊的最基本的結構圖,有三個部分組成:
- 視圖
我們的內容展示區域,相當於最外層的一個展示層- 容器
容器的寬度是無限長的,容納我們所有需要切換的內容,滑塊的左右滑動,實質上是容器的左右移動(left),而每個滑塊相對於容器其實是靜止的- 滑塊
一個個的內容
那么根據這個結構,可以用如下html代碼來表示:
<!-- 視圖層 --> <div class="swiper"> <!-- 容器 --> <div class="swiper-container"> <!-- 滑塊 --> <div class="swiper-item" style="background: #000">1</div> <div class="swiper-item" style="background: #4269eb">2</div> <div class="swiper-item" style="background: #247902">3</div> </div> </div>
然后再配上css樣式:
.swiper { position: relative; width: 300px; /* 下面是為了讓大家看的更清楚,加的修飾 */ padding: 30px 0; margin: 0 auto; background: #FFB973; } .swiper .swiper-container { position: relative; /* 為啥要設置-300px呢,因為我想讓他默認在第二個滑塊的位置,一會會給大家演示 */ left: -300px; /* 讓容器盡可能的寬,這樣才能容納更多的滑塊 */ width: 10000%; /* 讓內部滑塊可以排成一行 */ display: flex; /* 下面是為了讓大家看的更清楚,加的修飾 */ background: red; padding: 15px 0; } .swiper .swiper-container .swiper-item { /* 寬度設置1%會按照外層視圖的寬度來鋪滿 */ width: 1%; height: 300px; background: #eee; /* 下面是為了讓大家看的更清楚,加的修飾 */ text-align: center; font-size: 40px; color: #fff; }
你就會看到這么個效果:

當然,你可以把我加的修飾css樣式都給去掉,然后再試試。
2.1.2 滑塊的滑動原理是什么?
如果你能夠理解上面的html結構的話,那我們就可以進行滑動的講解了。(如果還不理解的話,那就繼續往下看吧~或許會突然恍然大悟!)
上面我們提到了“滑塊容器”這個概念,滑塊的左右移動就是他來負責的
.swiper .swiper-container { position: relative; left: -300px; }
因為滑塊的寬度是和視圖的寬度一樣的,所以我們這里滑塊的寬度是300px,那么我們把容器的left設置為-300px,就相當於向左移動了一個滑塊的寬度,設置為-600px就表示向左移動了兩個滑塊的寬度,懂了吧,如果你想移動到某個滑塊,那么只需要知道這個滑塊的順序(從0開始),然后乘以滑塊寬度的相反數就行了,比如要移動到第三個滑塊,他的順序是2,那么就是2 * -300 = -600
看下面動圖演示:

但是好像並沒有出現滑動的動畫效果耶,廢話,還沒寫呢,有些童鞋可能喜歡用jquery
,習慣了他的animate動畫方法,說實話其實我不太喜歡,因為我覺得css自帶的動畫完全可以解決大部分需求,而且當你以后用了vue這種mvvm框架,你會發現jquery這種動畫方式很不實用!
扯遠了,不過今天我們不用css的animation
屬性,我們用另外一個屬性transition
就可以滿足,看名字你也能猜到,就是一個過渡屬性,詳細的用法請參考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions
我們把容器加上transition
屬性試試看哈:
.swiper .swiper-container { /* 省略... */ transition: left 0.2s ease-in-out; }

所以,我們寫的這段css代碼transition: left 0.2s ease-in-out;
是表示:如果元素的left值變更,那么會有一個0.2s的過渡動畫(補間動畫)
到這里,我覺得你應該能理解了吧,每個滑塊swiper-item
的左右滑動,並不是滑塊本身在移動,而是他的父元素swiper-container
容器在左右移動(left值變化),然后我們用transition
屬性來讓這個變化過程出現一個過渡動畫效果!
2.1.3 如何來觸發滑動?
上面我們扯了一堆html和css,接下來我們說點js吧。
“如何來觸發滑動?”,我們先不考慮手機端,就按照pc網頁來,那么觸發操作就是在容器上按住鼠標向左/右拖動,然后松開鼠標后,滑塊就會向左/右滑動。
整個流程都跟鼠標事件掛鈎:
-
mousedown
鼠標按下事件 -
mousemove
鼠標移動事件 -
mouseup
鼠標抬起事件
利用好這3個事件,我們就可以來實現鼠標控制滑塊移動了!!我們先來實現摁住鼠標向左、向右拖動滑塊。
既然我們的容器swiper-container
是負責左右移動的,那么我們就來監聽他的鼠標事件吧,首先用querySelector
獲取視圖和容器兩個元素節點:
// 首先獲取視圖層元素 const swiperEl = document.querySelector('.swiper'); // 在視圖層里邊查找容器元素 const containerEl = swiperEl.querySelector('.swiper-container');
獲取到容器元素后,就可以用他的addEventListener
來監聽事件了:
containerEl.addEventListener('mousedown', (event) => { console.log('鼠標按下了'); }); containerEl.addEventListener('mousemove', (event) => { console.log('鼠標移動了'); }); containerEl.addEventListener('mouseup', (event) => { console.log('鼠標抬起了'); });
看下動圖操作:

雖然我們可以成功的監聽到鼠標的操作事件,但是好像有點問題,我們期望的結果是,只有當鼠標按下后才會觸發鼠標移動操作,但是現在看來並沒有,所以可以考慮加一個狀態來控制。
let state = 0; // 鼠標默認狀態 containerEl.addEventListener('mousedown', (event) => { state = 1; // 設置為1表示按下了鼠標 console.log('鼠標按下了'); }); containerEl.addEventListener('mousemove', (event) => { if (state != 1) return; // 只有當state == 1時候才允許執行該事件 console.log('鼠標移動了'); }); containerEl.addEventListener('mouseup', (event) => { state = 0; // 恢復默認狀態 console.log('鼠標抬起了'); });

這樣就好多了!!!
那么鼠標事件有了,接下來要讓容器跟着鼠標左右動才行。
我們要知道,瀏覽器對於鼠標的任何操作,都會有一個坐標參數(pageX和pageY),所以,我們可以根據鼠標移動時候的坐標參數來計算容器的left值,你可以想象一下,當你摁下鼠標然后左右移動,鼠標每次移動相對於上次都會產生一個距離,我們是不是可以把容器的left值加上或者減去這個距離,從而達到一個拖動效果呢?記得前面我們回調函數里邊的event
參數了嗎,他就是鼠標當前操作的相關屬性,而我們目前只需要用到pageX屬性

下面我們來寫代碼,有個地方需要注意下,我們先把容器的transition
這個屬性給注釋掉,后面會解釋為什么?
.swiper .swiper-container { /* 省略... */ /* transition: left 0.2s ease-in-out; */ } 每一步的操作,都在注釋里邊詳細標注: // 首先獲取視圖層元素 const swiperEl = document.querySelector('.swiper'); // 在視圖層里邊查找容器元素 const containerEl = swiperEl.querySelector('.swiper-container'); let state = 0; // 鼠標默認狀態 let oldEvent = null; // 用來記錄鼠標上次的位置 // 獲取容器的初始left值 let left = containerEl.offsetLeft; containerEl.addEventListener('mousedown', (event) => { state = 1; // 設置為1表示按下了鼠標 oldEvent = event; // 當鼠標按下時候記錄初始位置 console.log('鼠標按下了'); }); containerEl.addEventListener('mousemove', (event) => { if (state != 1) return; // 只有當state == 1時候才允許執行該事件 // 用當前鼠標的位置來和上次鼠標的位置作比較 // 如果當前鼠標的pageX小於上次鼠標的pageX,那就表示鼠標在向左拖動,就需要把容器left值減去鼠標移動的距離 if (event.pageX < oldEvent.pageX) { left -= oldEvent.pageX - event.pageX; } else { left += event.pageX - oldEvent.pageX; } // 完事之后記得把當前鼠標的位置賦值給oldEvent oldEvent = event; // 最后再把left賦值給容器 containerEl.style.left = left + 'px'; console.log('鼠標移動了'); }); containerEl.addEventListener('mouseup', (event) => { state = 0; // 恢復默認狀態 console.log('鼠標抬起了'); });
運行看效果:

沒毛病,你看這個鼠標,他又白又。。。
可是,可是,你這鼠標松開后,也沒滑動到對應位置啊,額,額,前面我們不是講了嘛,滑塊順序、滑塊寬度還記得么?0 - 滑塊順序 * 滑塊寬度就會移動到這個滑塊,還記得不?
我們用index
來記錄當前滑塊的順序
let index = 0; // 記錄當前滑塊的順序(從0開始)
用itemWidth
來存儲滑塊的寬度
// 獲取到所有的滑塊元素 const itemEls = containerEl.querySelectorAll('.swiper-item'); // 獲取到滑塊的寬度 const itemWidth = itemEls[0].offsetWidth;
把我們的left
變量改一下,之前left
變量是直接獲取容器元素的left值,現在我們要根據index
來計算
// let left = containerEl.offsetLeft; // 存儲容器的left,這里我們根據index來計算初始容器的left值 let left = 0 - itemWidth * index; // 設置容器的初始位置 containerEl.style.left = left + 'px';
這樣我們只需要修改index
變量的值,那么容器初始位置就會發生變化。
然后,我們在鼠標按下的時候,記錄下坐標位置,在鼠標抬起的時候拿當前鼠標的位置和按下的位置作比較,來判斷用戶是向左划的,還是向右划的!

加一個變量,用來記錄鼠標按下的參數,並且在鼠標按下的時候進行賦值!
let startEvent = null; // 用來記錄鼠標按下時候的位置(最初位置) containerEl.addEventListener('mousedown', (event) => { state = 1; // 設置為1表示按下了鼠標 startEvent = oldEvent = event; // 當鼠標按下時候記錄初始位置 console.log('鼠標按下了'); });
那么鼠標抬起的時候,只需要和startEvent.pageX
做比較,就可以判斷出左滑還是右滑,左滑我們讓index + 1
,右滑就讓index - 1
,最終我們通過index
再來計算left
containerEl.addEventListener('mouseup', (event) => { state = 0; // 恢復默認狀態 // 鼠標抬起時候,和按下的坐標作比對,用來判斷是向左滑動還是向右滑動 // 向左滑動那么就是要顯示下一個滑塊,所以index要加1 if (event.pageX < startEvent.pageX) { index ++; } else { index --; } left = 0 - itemWidth * index; containerEl.style.left = left + 'px'; console.log('鼠標抬起了'); });

是不是像那么回事了,不過怎么沒滑動動畫呢,還記得我們注釋掉的那個transition
么,為什么要注釋掉呢,因為只有在鼠標抬起的那一刻才需要滑動動畫,左右拖動是根據鼠標位移距離來計算left,數值很小,完全不需要銜接動畫,所以,我們先把注釋掉那個transition
代碼單獨提取出來放到一個和swiper-container
同級的.move
類里邊,當鼠標抬起的時候,我們把swiper-container
追加一個move
類就行。
.swiper .swiper-container.move { transition: left 0.2s ease-in-out; } containerEl.addEventListener('mouseup', (event) => { state = 0; // 恢復默認狀態 // 鼠標抬起時候,和按下的坐標作比對,用來判斷是向左滑動還是向右滑動 // 向左滑動那么就是要顯示下一個滑塊,所以index要加1 if (event.pageX < startEvent.pageX) { index ++; } else { index --; } // 追加一個move樣式 containerEl.className += ' move'; // 當過度動畫結束后,一定要把這個類給移除掉 containerEl.addEventListener('transitionend', () => { // 正則替換 \s+ 表示一個或多個空白字符 containerEl.className = containerEl.className.replace(/\s+move/, ''); }) left = 0 - itemWidth * index; containerEl.style.left = left + 'px'; console.log('鼠標抬起了'); });
注意觀察swiper-container
的dom節點:

仔細看上面的動圖,第一個和最后一個滑動的時候是不是越界了,那么我們只需要判斷index
就行,看代碼:
containerEl.addEventListener('mouseup', (event) => { state = 0; // 恢復默認狀態 // 鼠標抬起時候,和按下的坐標作比對,用來判斷是向左滑動還是向右滑動 // 向左滑動那么就是要顯示下一個滑塊,所以index要加1 if (event.pageX < startEvent.pageX) { index ++; } else { index --; } // 防止滑塊越界 // 如果當前滑塊是第一個,向右滑動后,回到第一個滑塊 // 如果是最后一個,向左滑動后,回到最后一個滑塊 if (index < 0) { index = 0; } else if (index > itemEls.length - 1) { index = itemEls.length - 1; } // 追加一個move樣式 containerEl.className += ' move'; // 當過度動畫結束后,一定要把這個類給移除掉 containerEl.addEventListener('transitionend', () => { // 正則替換 \s+ 表示一個或多個空白字符 containerEl.className = containerEl.className.replace(/\s+move/, ''); }) left = 0 - itemWidth * index; containerEl.style.left = left + 'px'; console.log('鼠標抬起了'); });

我們擴展出的三個問題,基本上都解決了。
而且到目前為止,其實你已經實現了一個基本的滑塊功能了,只不過略顯粗糙!
2.2 自動播放的原理
回到我們的功能列表,我們來看下第四條“自動播放”,第一個想到的是setInterval
setInterval(() => { // 這個回調會每隔2秒執行一次 }, 2000);
所以,我們只需要在這個回調函數里邊寫上讓滑塊滑動的代碼不就行了?
我們是用index
變量來控制當前滑塊的,那么每隔2秒讓index
加1,最后再根據index
計算出left
的值,不就可以了?
setInterval(() => { // 默認向左滑動 index ++; // 如果滑動到最后一個滑塊,則回到第一個滑塊 if (index > itemEls.length - 1) { index = 0; } // 下面的代碼跟我們鼠標抬起的事件的代碼一樣的,要不要考慮簡單的封裝一下? // 追加一個move樣式 containerEl.className += ' move'; // 當過度動畫結束后,一定要把這個類給移除掉 containerEl.addEventListener('transitionend', () => { // 正則替換 \s+ 表示一個或多個空白字符 containerEl.className = containerEl.className.replace(/\s+move/, ''); }) left = 0 - itemWidth * index; containerEl.style.left = left + 'px'; }, 2000);

關於重復邏輯的問題,我們會在第二部分寫插件時候進行封裝,這部分,我們只講原理,當然如果你是個強迫症患者,可以自己試着封裝個函數。
不過他老是這么自動播放也不是個事,有時候我想看看內容,還沒看完呢,就自動划走了,所以,我們可以當鼠標放在容器上的時候,停止播放,鼠標移開后又恢復自動播放
mouseover
鼠標移動到某個元素上mouseout
鼠標在某個元素上移開
我們還是在容器上監聽這兩個事件,並用一個狀態autoplay
來控制播放:
// 自動播放狀態 let autoplay = true; setInterval(() => { if (!autoplay) return; // 默認向左滑動 index ++; // 如果滑動到最后一個滑塊,則回到第一個滑塊 if (index > itemEls.length - 1) { index = 0; } // 追加一個move樣式 containerEl.className += ' move'; // 當過度動畫結束后,一定要把這個類給移除掉 containerEl.addEventListener('transitionend', () => { // 正則替換 \s+ 表示一個或多個空白字符 containerEl.className = containerEl.className.replace(/\s+move/, ''); }) left = 0 - itemWidth * index; containerEl.style.left = left + 'px'; }, 2000); containerEl.addEventListener('mouseover', () => { // 鼠標移動到容器上,停止播放 autoplay = false; }); containerEl.addEventListener('mouseout', () => { // 鼠標從容器上移開,恢復播放 autoplay = true; });

當然,還有其他的方法來控制自動播放,比如用clearInterval
函數等。
3. 結尾
至此,我們的原理都講的差不多了,有遺漏的地方,還望指出,那么在第二部分,我會和大家一塊來把寫的雜七雜八的代碼做一個封裝,讓我們的代碼插件化,適應更多的場景。
作者:Mr_芝麻
鏈接:https://www.jianshu.com/p/22accec4d17b
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。