一款輪播元件的誕生
1. 前言
早在幾個月前,就想自己動手寫個輪播圖元件,因此也看了許多文章,斷斷續續過了幾個月,今天終於有時間騰出手來給此外掛做個總結,因此有了這篇文章。話不多說,先上 Demo, 效果如下:
2. HTML and CSS
本文不討論html,css的實現方式,直接貼上程式碼
``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Suporka Vue App</title> </head> <body> <div id="carousal"> <!--左箭頭--> <button type="button" class="suporka-carousel__arrow suporka-carousel__arrow--left" style="" id="suporka-prev-btn" > < </button> <div id="wrapper"> <div class="box"> <img src="http://h5.sztoda.cn/static/img/loveLetter/teacher/teacher1.jpg" alt="" /> </div> <div class="box"> <img src="http://h5.sztoda.cn/static/img/loveLetter/teacher/teacher2.jpg" alt="" /> </div> <div class="box"> <img src="http://h5.sztoda.cn/static/img/loveLetter/teacher/teacher3.jpg" alt="" /> </div> <div class="box"> <img src="http://h5.sztoda.cn/static/img/loveLetter/teacher/teacher4.jpg" alt="" /> </div> </div> <!--右箭頭--> <button type="button" class="suporka-carousel__arrow suporka-carousel__arrow--right" style="" id="suporka-next-btn" > > </button> </div> <script src="https://cdn.jsdelivr.net/npm//*樣式程式碼*/ * { margin: 0; padding: 0; } div { margin: 0; border: 0; padding: 0; } #carousal { width: 557px; overflow: hidden; position: relative; } #wrapper { display: box; /* OLD - Android 4.4- */ display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */ display: -ms-flexbox; /* TWEENER - IE 10 */ display: -webkit-flex; /* NEW - Chrome */ display: flex; -webkit-flex-wrap: nowrap; -moz-flex-wrap: nowrap; -ms-flex-wrap: nowrap; -o-flex-wrap: nowrap; flex-wrap: nowrap; white-space: nowrap; position: relative; left: 0; } .suporka-carousel__arrow { /* display: none; */ border: none; outline: none; padding: 0; margin: 0; height: 36px; width: 36px; cursor: pointer; transition: 0.3s; border-radius: 50%; background-color: rgba(31, 45, 61, 0.5); color: #fff; position: absolute; top: 50%; z-index: 10; transform: translateY(-50%); text-align: center; font-size: 12px; font-weight: 600; } .suporka-carousel__arrow--right { right: -38px; } .suporka-carousel__arrow--left { left: -38px; } #carousal:hover > .suporkal-carousel__arrow { display: block; } #carousal:hover .suporka-carousel__arrow--right { right: 16px; } #carousal:hover .suporka-carousel__arrow--left { left: 16px; } #suporka-dot { position: absolute; bottom: 20px; left: 50%; transform: translate(-50%, 0); } #suporka-dot span { width: 10px; height: 10px; border-radius: 50%; margin: 0 10px; display: inline-block; background: #999; } #suporka-dot .suporka-dot--acitve { background: #fff; }
3. javascript 實現輪播圖功能
本外掛是用 es6 寫的,當然考慮相容性,你可以選擇 babel 進行編譯,本文不做闡述。
1. 首先,建立一個 carousal 類
它有一些預設引數,如time(圖片輪播間隔),transition (轉場動畫時間),autoScroll(是否自動輪播),showDot(是否顯示底部小圓點)。然後將頁面的一些元素掛載在類的屬性上。
class Carousal { constructor(userOption) { this.option = { time: 4000, transition: 0.8, autoScroll: true, showDot: false }; // 當前索引 this.number = 1; // 定時器 this.timer = null; this.interval = null; this.carousal = document.getElementById("carousal"); this.wrapper = document.querySelector("#wrapper"); this.childrenLength: document.getElementById("wrapper").children.length; this.init(userOption); } }
2. 初始化dom
當然,預設引數是可以修改的,所以類傳入了一個 userOption 物件, 在建構函式中將使用者設定的引數覆蓋預設引數,在this.init(userOption) 方法中執行覆蓋。
輪播圖輪播的原理是:在輪播圖組首位新增一個末點陣圖片的副本,同時也在輪播圖末位新增一個首點陣圖片的副本,大概就是 5 1 2 3 4 5 1
, 此時共有7張圖片,當向右輪播至第七張圖片‘1’ 時, 取消transition後輪播圖定位至第二張圖片 ‘1’, 此時再度開啟transition 。同理,向左輪播至第一張圖片“5”時,也會取消transition後輪播圖定位至第六張圖片 ‘5’, 而後再度開啟 transition。
因此,我們需要手動在dom結構中插入這兩個首尾圖片。pushItem() 方法正是為此而生。
class Carousal {
//...
init(userOption) {
// 合併使用者配置
if (Object.assign) {
Object.assign(this.option, userOption);
} else {
// 不支援 Object.assign 就呼叫 extend 方法
this.extend(this.option, userOption, true);
}
// 設定動畫 transition
this.wrapper.style.transition = `all ${this.option.transition}s`;
this.wrapper.style["-moz-transition"] = `all ${this.option.transition}s`;
this.wrapper.style["-webkit-transition"] = `all ${this.option.transition}s`;
this.wrapper.style["-o-transition"] = `all ${this.option.transition}s`;
// 首尾新增元素
this.pushItem();
}
// 合併屬性方法
extend(o, n, override) {
for (var p in n) {
if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override))
o[p] = n[p];
}
}
// 初始化新增首尾子元素
pushItem() {
let movePx = this.carousal.offsetWidth; // 獲取輪播圖寬度
let first = this.wrapper.children[0].cloneNode(true);
let last = this.wrapper.children[this.childrenLength - 1].cloneNode(true);
let parent = this.wrapper;
parent.appendChild(first);
parent.insertBefore(last, parent.children[0]);
this.wrapper.style.left =
this.wrapper.offsetLeft - movePx + "px";
}
}
插入輪播圖片之後,判斷是否需要插入底部小圓點。requestAnimFrame()用於實現持續的動畫效果。
class Carousal {
init() {
//...續 this.pushItem();
if (this.option.showDot) {
let node = document.createElement('div');
node.setAttribute('id', 'suporka-dot');
node.innerHTML = `${'<span></span>'.repeat(this.childrenLength)}`;
this.carousal.appendChild(node);
this.dot = document.getElementById('suporka-dot');
this.dot.firstChild.setAttribute('class', 'suporka-dot--acitve');
}
// 判斷是否開啟自動輪播,如是則自動輪播
if (this.option.autoScroll) this.requestAnimFrame(this.autoMove());
}
requestAnimFrame() {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
}
);
}
}
3. 加入事件監聽
監聽滑鼠移入事件,當滑鼠移入的時候,停止自動滾動。
監聽左右按鈕的點選,執行上一張,下一張圖的輪播效果。
class Carousal {
init() {
//...續 if (this.option.autoScroll) this.requestAnimFrame(this.autoMove());
this.addEventListener();
}
// 新增事件
addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
}
// 事件監聽
addEventListener() {
if (this.option.autoScroll) {
this.addEvent(this.carousal, 'mouseover', event => {
clearInterval(this.interval);
});
this.addEvent(this.carousal, 'mouseout', event => {
this.autoMove();
});
}
let prev = document.getElementById('suporka-prev-btn');
let next = document.getElementById('suporka-next-btn');
if (prev && next) {
this.addEvent(prev, 'click', event => {
this.prev();
});
this.addEvent(next, 'click', event => {
this.next();
});
}
}
}
4. 自動輪播
定時動畫,並且如果存在底部小圓點,修改其類名,達到與輪播圖同步的效果。
// 自動輪播
class Carousal {
// ...
autoMove() {
let movePx = this.carousal.offsetWidth;
this.interval = setInterval(() => {
this.number += 1;
this.wrapper.style.left = 0 - movePx * this.number + 'px';
if (this.number === this.childrenLength + 1) this.startMove();
if (this.dot)
this.setDotClass(
this.dot.children,
this.number - 1,
'suporka-dot--acitve'
);
}, this.option.time);
}
// 開始移動
startMove() {
this.number = 1;
this.timer = setTimeout(() => {
this.wrapper.style.transition = `none`;
this.wrapper.style.left = -this.carousal.offsetWidth + 'px';
setTimeout(() => {
this.wrapper.style.transition = `all ${
this.option.transition
}s`;
}, 100);
}, this.option.transition * 1000);
}
// 設定小圓點樣式
setDotClass(parent, index, cls) {
// 沒有小圓點就返回
if (!this.dot) return false;
for (let i = 0; i < parent.length; i++) {
removeClass(parent[i], cls);
}
addClass(parent[index], cls);
}
}
// 三個類名操作方法
function hasClass(ele, cls) {
if (ele.className)
return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
else return false;
}
function addClass(ele, cls) {
if (!hasClass(ele, cls)) ele.className += ' ' + cls;
}
function removeClass(ele, cls) {
if (hasClass(ele, cls)) {
let reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
ele.className = ele.className.replace(reg, ' ');
}
}
5. 實現上一張,下一張輪播功能
class Carousal {
//...
// prev上一張
prev() {
let movePx = this.carousal.offsetWidth;
this.number -= 1;
this.wrapper.style.left = 0 - movePx * this.number + 'px';
if (this.number === 0) this.goLastOne();
if (this.dot)
this.setDotClass(
this.dot.children,
this.number - 1,
'suporka-dot--acitve'
);
}
// 下一張
next() {
let movePx = this.carousal.offsetWidth;
this.number += 1;
this.wrapper.style.left = 0 - movePx * this.number + 'px';
if (this.number === this.childrenLength + 1) this.startMove();
if (this.dot)
this.setDotClass(
this.dot.children,
this.number - 1,
'suporka-dot--acitve'
);
}
// 去到最後一張
goLastOne() {
this.number = this.childrenLength;
this.timer = setTimeout(() => {
this.wrapper.style.transition = `none`;
this.wrapper.style.left =
-this.carousal.offsetWidth * this.childrenLength + 'px';
setTimeout(() => {
this.wrapper.style.transition = `all ${
this.option.transition
}s`;
}, 100);
}, this.option.transition * 1000);
}
}
4.優化及其他
最後,程式碼需經過babel轉譯,並且以 umd 的形式支援瀏覽器直接引入,requirejs 及 commonjs 匯入,詳細做法可以參考我之前的一篇文章《ES6 手寫一個“辨色”小遊戲》。也可以參考我在 github 上的程式碼, 歡迎 fork and star .