摘要:綁定輪播事件然后是鼠標(biāo)移入移出事件的綁定鼠標(biāo)移入移出事件移入時(shí)停止輪播播放的定時(shí)器,移出后自動(dòng)開始下一張的播放。
通過上一篇文章的學(xué)習(xí),我們基本掌握了一個(gè)輪子的封裝和開發(fā)流程。那么這次將帶大家開發(fā)一個(gè)更有難度的項(xiàng)目——輪播圖,希望能進(jìn)一步加深大家對于面向?qū)ο蟛寮_發(fā)的理解和認(rèn)識(shí)。
So, Let"s begin!
目前項(xiàng)目使用 ES5及UMD 規(guī)范封裝,所以在前端暫時(shí)只支持標(biāo)簽的引入方式,未來會(huì)逐步用 ES6 進(jìn)行重構(gòu)
演示地址:carousel carousel-mobile
Github:csdwheels
如果覺得好用就點(diǎn)個(gè)Star吧~(〃"▽"〃)
老規(guī)矩,在寫代碼之前,我們需要對要開發(fā)的東西有個(gè)感性的認(rèn)識(shí),比如你可以先在腦中大致過一遍最終的項(xiàng)目效果是如何的,而在這里你可以直接看上面的動(dòng)態(tài)圖or項(xiàng)目頁面進(jìn)行體驗(yàn)。實(shí)際的開發(fā)階段之前,我們更要對插件的邏輯思路有一個(gè)整體的分析,這樣在開發(fā)時(shí)才會(huì)更有效率,并且可以有效避免因?yàn)樗悸凡磺逦鴮?dǎo)致的問題。
首先來看看Web輪播的效果及交互有哪些:
每隔一段時(shí)間自動(dòng)輪播
左右箭頭可切換輪播
圓點(diǎn)可切換輪播
當(dāng)鼠標(biāo)在輪播區(qū)域內(nèi)時(shí),輪播暫停;離開區(qū)域后,輪播重新開始
輪播切換時(shí),會(huì)有一個(gè)勻速運(yùn)動(dòng)的動(dòng)畫效果
當(dāng)向右切換到最后一張時(shí),會(huì)自動(dòng)循環(huán)到第一張;向左切換到第一張時(shí),循環(huán)到最后一張
如上幾點(diǎn),可以說都是一個(gè)輪播圖必須實(shí)現(xiàn)的經(jīng)典效果了。其他效果先忽略,第六點(diǎn)對于新手來說明顯是最有難度的,事實(shí)上這個(gè)效果有個(gè)常見的名字——無縫輪播。“無縫”也可以理解為無限循環(huán),其實(shí)就是可以讓輪播朝著一個(gè)方向一直切換,并且自動(dòng)在切換到頭尾圖片時(shí)循環(huán)。
比如現(xiàn)在有五張圖片,我們把它們編號(hào)為:
1 2 3 4 5
要實(shí)現(xiàn)上面的效果,你可能會(huì)想到在切換至頭尾時(shí)加個(gè)判斷,強(qiáng)制改變圖片位置,但是如果這么做的話,當(dāng)你最后一張圖切換回第一張圖時(shí)就會(huì)出現(xiàn)空白,因此還需要在頭尾分別添加一個(gè)尾部和頭部的元素作為位置改變時(shí)的過渡:
5 1 2 3 4 5 1
有了這兩張輔助圖,上面的效果就能順利實(shí)現(xiàn)了。到此,項(xiàng)目的基礎(chǔ)思路分析完畢,讓我們進(jìn)入編碼階段吧!
基本架構(gòu)正式開始之前,還是需要先把項(xiàng)目的基本架構(gòu)搭建起來:
(function(root, factory) { if (typeof define === "function" && define.amd) { define([], factory); } else if (typeof module === "object" && module.exports) { module.exports = factory(); } else { root.Carousel = factory(); } })(typeof self !== "undefined" ? self : this, function() { "use strict"; // ID-NAMES var ID = { CAROUSEL_WRAP: "#carouselWrap", CAROUSEL_DOTS: "#carouselDots", ARROW_LEFT: "#arrowLeft", ARROW_RIGHT: "#arrowRight" }; var CLASS = { CAROUSEL_WRAP: "carousel-wrap", CAROUSEL_IMG: "carousel-image", CAROUSEL_DOTS_WRAP: "carousel-buttons-wrap", CAROUSEL_DOTS: "carousel-buttons", CAROUSEL_DOT: "carousel-button", CAROUSEL_DOT_ON: "carousel-button on", CAROUSEL_ARROW_LEFT: "carousel-arrow arrow-left", CAROUSEL_ARROW_RIGHT: "carousel-arrow arrow-right" }; // Polyfills function 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; } } // 合并對象 function extend(o, n, override) { for (var p in n) { if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override)) o[p] = n[p]; } } // 輪播-構(gòu)造函數(shù) var Carousel = function (selector, userOptions) { var _this = this; // 合并配置 extend(this.carouselOptions, userOptions, true); // 獲取輪播元素 _this.carousel = document.querySelector(selector); // 初始化輪播列表 _this.carousel.appendChild(_this.getImgs()); // 獲取輪播列表 _this.carouselWrap = document.querySelector(ID.CAROUSEL_WRAP); // 每隔 50ms 檢測一次輪播是否加載完成 var checkInterval = 50; var checkTimer = setInterval(function () { // 檢測輪播是否加載完成 if (_this.isCarouselComplete()) { // 加載完成后清除定時(shí)器 clearInterval(checkTimer); // 初始化輪播 _this.initCarousel(); // 初始化圓點(diǎn) _this.initDots(); // 初識(shí)化箭頭 _this.initArrows(); } }, checkInterval); }; // 輪播-原型對象 Carousel.prototype = { carouselOptions: { // 是否顯示輪播箭頭 showCarouselArrow: true, // 是否顯示輪播圓點(diǎn) showCarouselDot: true, // 輪播自動(dòng)播放間隔 carouselInterval: 3000, // 輪播動(dòng)畫總時(shí)間 carouselAnimateTime: 150, // 輪播動(dòng)畫間隔 carouselAnimateInterval: 10 }, isCarouselComplete: function () { // 檢測頁面圖片是否加載完成 var completeCount = 0; for (var i = 0; i < this.carouselWrap.children.length; i++) { if (this.carouselWrap.children[i].complete) { completeCount++; } } return completeCount === this.carouselWrap.children.length ? true : false; } }; return Carousel; });
addEvent()和extend()函數(shù)上篇已經(jīng)介紹過了,構(gòu)造函數(shù)中各種配置項(xiàng)也都是項(xiàng)目中要用到的,不必多說。這里的重點(diǎn)是checkTimer定時(shí)器,它的作用是每隔一定時(shí)間去檢查頁面上的圖片元素是否全部加載完畢,如果加載完畢再進(jìn)行項(xiàng)目的初始化。
為什么需要這么做呢?因?yàn)槲覀兊膱D片元素是在JS中使用DOM動(dòng)態(tài)加載的,所以可能會(huì)出現(xiàn)圖片還沒加載完成就執(zhí)行了JS中的一些邏輯語句,導(dǎo)致不能通過DOM API正確獲取到圖片和對應(yīng)元素屬性的現(xiàn)象。因此,在isCarouselComplete()函數(shù)中我們利用img元素的complete屬性,判斷當(dāng)前頁面上的所有圖片是否加載完成,然后就能保證DOM屬性的正確獲取了。
完成initCarousel()函數(shù):
initCarousel: function(selector, userOptions) { // 獲取輪播數(shù)量 this.carouselCount = this.carouselWrap.children.length; // 設(shè)置輪播 this.setCarousel(); // 初始化輪播序號(hào) this.carouselIndex = 1; // 初始化定時(shí)器 this.carouselIntervalr = null; // 每次位移量 = 總偏移量 / 次數(shù) this.carouselAnimateSpeed = this.carouselWidth / (this.carouselOptions.carouselAnimateTime / this.carouselOptions.carouselAnimateInterval); // 判斷是否處于輪播動(dòng)畫狀態(tài) this.isCarouselAnimate = false; // 判斷圓點(diǎn)是否點(diǎn)擊 this.isDotClick = false; // 綁定輪播圖事件 this.bindCarousel(); // 播放輪播 this.playCarousel(); }
通過this.carouselWidth / (this.carouselOptions.carouselAnimateTime / this.carouselOptions.carouselAnimateInterval)這個(gè)公式,可以計(jì)算出每次輪播動(dòng)畫位移的偏移量,后面完成動(dòng)畫函數(shù)時(shí)會(huì)用到。
在setCarousel()里進(jìn)行輪播基本屬性的設(shè)置:
setCarousel: function () { // 復(fù)制首尾節(jié)點(diǎn) var first = this.carouselWrap.children[0].cloneNode(true); var last = this.carouselWrap.children[this.carouselCount - 1].cloneNode(true); // 添加過渡元素 this.carouselWrap.insertBefore(last, this.carouselWrap.children[0]); this.carouselWrap.appendChild(first); // 設(shè)置輪播寬度 this.setWidth(this.carousel, this.carouselOptions.carouselWidth); // 設(shè)置輪播高度 this.setHeight(this.carousel, this.carouselOptions.carouselHeight); // 獲取輪播寬度 this.carouselWidth = this.getWidth(this.carousel); // 設(shè)置初始位置 this.setLeft(this.carouselWrap, -this.carouselWidth); // 設(shè)置輪播長度 this.setWidth(this.carouselWrap, this.carouselWidth * this.carouselWrap.children.length); }
添加首尾的過渡元素、設(shè)置高度寬度等。
綁定輪播事件然后是鼠標(biāo)移入移出事件的綁定:
playCarousel: function () { var _this = this; this.carouselIntervalr = window.setInterval(function() { _this.nextCarousel(); }, this.carouselOptions.carouselInterval); }, bindCarousel: function () { var _this = this; // 鼠標(biāo)移入移出事件 addEvent(this.carousel, "mouseenter", function(e) { clearInterval(_this.carouselIntervalr); }); addEvent(this.carousel, "mouseleave", function(e) { _this.playCarousel(); }); }
移入時(shí)停止輪播播放的定時(shí)器,移出后自動(dòng)開始下一張的播放。
完成nextCarousel()和prevCarousel()函數(shù):
prevCarousel: function () { if (!this.isCarouselAnimate) { // 改變輪播序號(hào) this.carouselIndex--; if (this.carouselIndex < 1) { this.carouselIndex = this.carouselCount; } // 設(shè)置輪播位置 this.moveCarousel(this.isFirstCarousel(), this.carouselWidth); if (this.carouselOptions.showCarouselDot) { // 顯示當(dāng)前圓點(diǎn) this.setDot(); } } }, nextCarousel: function () { if (!this.isCarouselAnimate) { this.carouselIndex++; if (this.carouselIndex > this.carouselCount) { this.carouselIndex = 1; } this.moveCarousel(this.isLastCarousel(), -this.carouselWidth); if (this.carouselOptions.showCarouselDot) { // 顯示當(dāng)前圓點(diǎn) this.setDot(); } } }
功能是一樣的,改變輪播序號(hào),然后進(jìn)行輪播的移動(dòng)。在moveCarousel()中完成對過渡元素的處理:
moveCarousel: function (status, carouselWidth) { var left = 0; if (status) { left = -this.carouselIndex * this.carouselWidth; } else { left = this.getLeft(this.carouselWrap) + carouselWidth; } this.setLeft(this.carouselWrap, left); }
輪播相關(guān)屬性和事件的設(shè)置就完成了。
綁定圓點(diǎn)事件接下來是小圓點(diǎn)的事件綁定:
bindDots: function () { var _this = this; for (var i = 0, len = this.carouselDots.children.length; i < len; i++) { (function(i) { addEvent(_this.carouselDots.children[i], "click", function (ev) { // 獲取點(diǎn)擊的圓點(diǎn)序號(hào) _this.dotIndex = i + 1; if (!_this.isCarouselAnimate && _this.carouselIndex !== _this.dotIndex) { // 改變圓點(diǎn)點(diǎn)擊狀態(tài) _this.isDotClick = true; // 改變圓點(diǎn)位置 _this.moveDot(); } }); })(i); } }, moveDot: function () { // 改變當(dāng)前輪播序號(hào) this.carouselIndex = this.dotIndex; // 設(shè)置輪播位置 this.setLeft(this.carouselWrap, -this.carouselIndex * this.carouselWidth); // 重設(shè)當(dāng)前圓點(diǎn)樣式 this.setDot(); }, setDot: function () { for (var i = 0, len = this.carouselDots.children.length; i < len; i++) { this.carouselDots.children[i].setAttribute("class", CLASS.CAROUSEL_DOT); } this.carouselDots.children[this.carouselIndex - 1].setAttribute("class", CLASS.CAROUSEL_DOT_ON); }
功能很簡單,點(diǎn)擊圓點(diǎn)后,跳轉(zhuǎn)到對應(yīng)序號(hào)的輪播圖,并重設(shè)小圓點(diǎn)樣式。
綁定箭頭事件最后,還需要綁定箭頭事件:
bindArrows: function () { var _this = this; // 箭頭點(diǎn)擊事件 addEvent(this.arrowLeft, "click", function(e) { _this.prevCarousel(); }); addEvent(this.arrowRight, "click", function(e) { _this.nextCarousel(); }); }
這樣,一個(gè)沒有動(dòng)畫的無縫輪播效果就完成了,見下圖:
上一節(jié)我們分析后的思路基本是實(shí)現(xiàn)了,但是輪播切換時(shí)的動(dòng)畫效果又該怎么實(shí)現(xiàn)呢?
既然要實(shí)現(xiàn)動(dòng)畫,那我們先要找到產(chǎn)生動(dòng)畫的源頭——即讓輪播發(fā)生切換的moveCarousel()函數(shù)。因此,我們需要先對它進(jìn)行修改:
moveCarousel: function (target, speed) { var _this = this; _this.isCarouselAnimate = true; function animateCarousel () { if ((speed > 0 && _this.getLeft(_this.carouselWrap) < target) || (speed < 0 && _this.getLeft(_this.carouselWrap) > target)) { _this.setLeft(_this.carouselWrap, _this.getLeft(_this.carouselWrap) + speed); timer = window.setTimeout(animateCarousel, _this.carouselOptions.carouselAnimateInterval); } else { window.clearTimeout(timer); // 重置輪播狀態(tài) _this.resetCarousel(target, speed); } } var timer = animateCarousel(); }
改造之后的moveCarousel()函數(shù)接受兩個(gè)參數(shù),target表示要移動(dòng)到的輪播的位置,speed即為我們前面計(jì)算出的那個(gè)偏移量的值。然后通過animateCarousel()函數(shù)進(jìn)行setTimeout()的遞歸調(diào)用,模擬出一種定時(shí)器的效果,當(dāng)判斷未到達(dá)目標(biāo)位置時(shí)繼續(xù)遞歸,如果到達(dá)后就重置輪播狀態(tài):
// 不符合位移條件,把當(dāng)前l(fā)eft值置為目標(biāo)值 this.setLeft(this.carouselWrap, target); //如當(dāng)前在輔助圖上,就歸位到真的圖上 if (target > -this.carouselWidth ) { this.setLeft(this.carouselWrap, -this.carouselCount * this.carouselWidth); } if (target < (-this.carouselWidth * this.carouselCount)) { this.setLeft(this.carouselWrap, -this.carouselWidth); }
重置的過程和前面的實(shí)現(xiàn)是一樣的,不再贅述。完成新的moveCarousel()函數(shù)之后,還需要對prevCarousel()及nextCarousel()進(jìn)行改造:
prevCarousel: function () { if (!this.isCarouselAnimate) { // 改變輪播序號(hào) this.carouselIndex--; if (this.carouselIndex < 1) { this.carouselIndex = this.carouselCount; } // 設(shè)置輪播位置 this.moveCarousel(this.getLeft(this.carouselWrap) + this.carouselWidth, this.carouselAnimateSpeed); if (this.carouselOptions.showCarouselDot) { // 顯示當(dāng)前圓點(diǎn) this.setDot(); } } }, nextCarousel: function () { if (!this.isCarouselAnimate) { this.carouselIndex++; if (this.carouselIndex > this.carouselCount) { this.carouselIndex = 1; } this.moveCarousel(this.getLeft(this.carouselWrap) - this.carouselWidth, -this.carouselAnimateSpeed); if (this.carouselOptions.showCarouselDot) { // 顯示當(dāng)前圓點(diǎn) this.setDot(); } } },
其實(shí)就替換了一下moveCarousel()調(diào)用的參數(shù)而已。完成這幾個(gè)函數(shù)的改造后,動(dòng)畫效果初步實(shí)現(xiàn)了:
在頁面上進(jìn)行實(shí)際測試的過程中,我們可能偶爾會(huì)發(fā)現(xiàn)有卡頓的情況出現(xiàn),這主要是因?yàn)橛?b>setTimeout()遞歸后模擬動(dòng)畫的時(shí)候產(chǎn)生的(直接用setInterval()同樣會(huì)出現(xiàn)這種情況),所以我們需要用requestAnimationFrame這個(gè)HTML5的新API進(jìn)行動(dòng)畫效率的優(yōu)化,再次改造moveCarousel()函數(shù):
moveCarousel: function (target, speed) { var _this = this; _this.isCarouselAnimate = true; function animateCarousel () { if ((speed > 0 && _this.getLeft(_this.carouselWrap) < target) || (speed < 0 && _this.getLeft(_this.carouselWrap) > target)) { _this.setLeft(_this.carouselWrap, _this.getLeft(_this.carouselWrap) + speed); timer = window.requestAnimationFrame(animateCarousel); } else { window.cancelAnimationFrame(timer); // 重置輪播狀態(tài) _this.resetCarousel(target, speed); } } var timer = window.requestAnimationFrame(animateCarousel); }
兩種方法的調(diào)用方式是類似的,但是在實(shí)際看起來,動(dòng)畫卻流暢了不少,最重要的,它讓我們動(dòng)畫的效率得到了很大提升。
到這里,我們的開發(fā)就結(jié)束了嗎?
用上面的方式實(shí)現(xiàn)完動(dòng)畫后,當(dāng)你點(diǎn)擊圓點(diǎn)時(shí),輪播的切換是跳躍式的,并沒有達(dá)到我們開頭gif中那種完成后的效果。要讓任意圓點(diǎn)點(diǎn)擊后的切換效果仍然像相鄰圖片一樣的切換,這里還需要一種新的思路。
假如我們當(dāng)前在第一張圖片,這時(shí)候的序號(hào)為1,而點(diǎn)擊的圓點(diǎn)對應(yīng)圖片序號(hào)為5的話,我們可以這么處理:在序號(hào)1對應(yīng)圖片節(jié)點(diǎn)的后面插入一個(gè)序號(hào)5對應(yīng)的圖片節(jié)點(diǎn),然后讓輪播切換到這張新增的圖片,切換完成后,立即改變圖片位置為真正的序號(hào)5圖片,最后刪除新增的節(jié)點(diǎn),過程如下:
第一步:插入一個(gè)新節(jié)點(diǎn) 5 1 5 2 3 4 5 1第二步:改變圖片位置,節(jié)點(diǎn)順序不變
第三步:刪除新節(jié)點(diǎn),還原節(jié)點(diǎn)順序 5 1 2 3 4 5 1
用代碼實(shí)現(xiàn)出來就是這樣的:
moveDot: function () { // 改變輪播DOM,增加過渡效果 this.changeCarousel(); // 改變當(dāng)前輪播序號(hào) this.carouselIndex = this.dotIndex; // 重設(shè)當(dāng)前圓點(diǎn)樣式 this.setDot(); }, changeCarousel: function () { // 保存當(dāng)前節(jié)點(diǎn)位置 this.currentNode = this.carouselWrap.children[this.carouselIndex]; // 獲取目標(biāo)節(jié)點(diǎn)位置 var targetNode = this.carouselWrap.children[this.dotIndex]; // 判斷點(diǎn)擊圓點(diǎn)與當(dāng)前的相對位置 if (this.carouselIndex < this.dotIndex) { // 在當(dāng)前元素右邊插入目標(biāo)節(jié)點(diǎn) var nextNode = this.currentNode.nextElementSibling; this.carouselWrap.insertBefore(targetNode.cloneNode(true), nextNode); this.moveCarousel(this.getLeft(this.carouselWrap) - this.carouselWidth, -this.carouselAnimateSpeed); } if (this.carouselIndex > this.dotIndex) { // 在當(dāng)前元素左邊插入目標(biāo)節(jié)點(diǎn) this.carouselWrap.insertBefore(targetNode.cloneNode(true), this.currentNode); // 因?yàn)橄蜃筮叢迦牍?jié)點(diǎn)后,當(dāng)前元素的位置被改變,導(dǎo)致畫面有抖動(dòng)現(xiàn)象,這里重置為新的位置 this.setLeft(this.carouselWrap, -(this.carouselIndex + 1) * this.carouselWidth); this.moveCarousel(this.getLeft(this.carouselWrap) + this.carouselWidth, this.carouselAnimateSpeed); } }
需要注意的是,這里要判斷點(diǎn)擊的圓點(diǎn)序號(hào)與當(dāng)前序號(hào)的關(guān)系,也就是在當(dāng)前序號(hào)的左邊還是右邊,如果是左邊,還需要對位置進(jìn)行重置。最后一步,完成新增節(jié)點(diǎn)的刪除函數(shù)resetMoveDot():
resetCarousel: function (target, speed) { // 判斷圓點(diǎn)是否點(diǎn)擊 if (this.isDotClick) { // 重置圓點(diǎn)點(diǎn)擊后的狀態(tài) this.resetMoveDot(speed); } else { // 重置箭頭或者自動(dòng)輪播后的狀態(tài) this.resetMoveCarousel(target); } this.isDotClick = false; this.isCarouselAnimate = false; }, resetMoveDot: function (speed) { // 如果是圓點(diǎn)點(diǎn)擊觸發(fā)動(dòng)畫,需要?jiǎng)h除新增的過度節(jié)點(diǎn)并將輪播位置重置到實(shí)際位置 this.setLeft(this.carouselWrap, -this.dotIndex * this.carouselWidth); // 判斷點(diǎn)擊圓點(diǎn)和當(dāng)前圓點(diǎn)的相對位置 if (speed < 0) { this.carouselWrap.removeChild(this.currentNode.nextElementSibling); } else { this.carouselWrap.removeChild(this.currentNode.previousElementSibling); } },
查看一下效果:
大功告成!!
H5輪播在Web版輪播的實(shí)現(xiàn)中,我們對位置的控制是直接使用元素絕對定位后的left值實(shí)現(xiàn)的,這種辦法雖然兼容性好,但是效率相對是比較低的。在移動(dòng)端版本的實(shí)現(xiàn)中,我們就可以不用考慮這種兼容性的問題了,而可以盡量用更高效的方式實(shí)現(xiàn)動(dòng)畫效果。
如果大家對CSS3有所了解,那想必一定知道transform這個(gè)屬性。從字面上來講,它就是變形,改變的意思,而它的值大致包括旋轉(zhuǎn)rotate、扭曲skew、縮放scale和移動(dòng)translate以及矩陣變形matrix等幾種類型。我們今天需要用到的就是translate,通過使用它以及transition等動(dòng)畫屬性,可以更高效簡潔的實(shí)現(xiàn)移動(dòng)端圖片輪播的移動(dòng)。
由于基本思路與架構(gòu)和Web版是差不多的,而H5版是基于Web版重寫的,所以這里只說下需要改變的幾個(gè)地方。
替換Left的操作方法既然是用新屬性來實(shí)現(xiàn),那首先就要重寫setLeft()和getLeft()方法,這里我們直接替換為兩個(gè)新方法:
setLeft: function (elem, value) { elem.style.left = value + "px"; }, getLeft: function (elem) { return parseInt(elem.style.left); } setTransform: function(elem ,value) { elem.style.transform = "translate3d(" + value + "px, 0px, 0px)"; elem.style["-webkit-transform"] = "translate3d(" + value + "px, 0px, 0px)"; elem.style["-ms-transform"] = "translate3d(" + value + "px, 0px, 0px)"; }, getTransform: function() { var x = this.carouselWrap.style.transform || this.carouselWrap.style["-webkit-transform"] || this.carouselWrap.style["-ms-transform"]; x = x.substring(12); x = x.match(/(S*)px/)[1]; return Number(x); }
新版的方法功能與老版完全一直,只是實(shí)現(xiàn)所用到的方法不一樣了。接下來我們需要一個(gè)transition值的設(shè)置方法,通過這個(gè)動(dòng)畫屬性,連requestAnimationFrame的相關(guān)操作也不需要了:
setTransition: function(elem, value) { elem.style.transition = value + "ms"; }
有了這三個(gè)方法,接下來就可以重寫moveCarousel()、resetCarousel()和resetMoveCarousel()方法了:
moveCarousel: function(target) { this.isCarouselAnimate = true; this.setTransition(this.carouselWrap, this.carouselOptions.carouselDuration); this.setTransform(this.carouselWrap, target); this.resetCarousel(target); }, resetCarousel: function(target) { var _this = this; window.setTimeout(function() { // 重置箭頭或者自動(dòng)輪播后的狀態(tài) _this.resetMoveCarousel(target); _this.isCarouselAnimate = false; }, _this.carouselOptions.carouselDuration); }, resetMoveCarousel: function(target) { this.setTransition(this.carouselWrap, 0); // 不符合位移條件,把當(dāng)前l(fā)eft值置為目標(biāo)值 this.setTransform(this.carouselWrap, target); //如當(dāng)前在輔助圖上,就歸位到真的圖上 if (target > -this.carouselWidth) { this.setTransform(this.carouselWrap, -this.carouselCount * this.carouselWidth); } if (target < -this.carouselWidth * this.carouselCount) { this.setTransform(this.carouselWrap, -this.carouselWidth); } }
之所以在每次setTransform()改變位置之前都要重新設(shè)置transition的值,是因?yàn)?b>transition會(huì)使每次位置的改變都帶上動(dòng)畫效果,而我們在代碼中做的過渡操作又不希望用戶直接看到,因此,重設(shè)它的值后才能和以前的實(shí)現(xiàn)效果保持一致。
添加touch事件在移動(dòng)端上我們通常習(xí)慣用手指直接觸摸屏幕來操作應(yīng)用,所以Web端圓點(diǎn)和箭頭的交互方式這時(shí)候就顯得不那么合適了,取而代之的,我們可以改寫成觸摸的交互方式,也就是touch事件實(shí)現(xiàn)的效果:
bindCarousel: function() { var _this = this; // 鼠標(biāo)移入移出事件 addEvent(this.carousel, "touchstart", function(e) { if (!_this.isCarouselAnimate) { clearInterval(_this.carouselIntervalr); _this.carouselTouch.startX = _this.getTransform(); _this.carouselTouch.start = e.changedTouches[e.changedTouches.length - 1].clientX; } }); addEvent(this.carousel, "touchmove", function(e) { if (!_this.isCarouselAnimate && _this.carouselTouch.start != -1) { clearInterval(_this.carouselIntervalr); _this.carouselTouch.move = e.changedTouches[e.changedTouches.length - 1].clientX - _this.carouselTouch.start; _this.setTransform(_this.carouselWrap, _this.carouselTouch.move + _this.carouselTouch.startX); } }); addEvent(this.carousel, "touchend", function(e) { if (!_this.isCarouselAnimate && _this.carouselTouch.start != -1) { clearInterval(_this.carouselIntervalr); _this.setTransform(_this.carouselWrap, _this.carouselTouch.move + _this.carouselTouch.startX); var x = _this.getTransform(); x += _this.carouselTouch.move > 0 ? _this.carouselWidth * _this.carouselTouch.offset : _this.carouselWidth * -_this.carouselTouch.offset; _this.carouselIndex = Math.round(x / _this.carouselWidth) * -1; _this.moveCarousel( _this.carouselIndex * -_this.carouselWidth ); if (_this.carouselIndex > _this.carouselCount) { _this.carouselIndex = 1; } if (_this.carouselIndex < 1) { _this.carouselIndex = _this.carouselCount; } _this.playCarousel(); } }); }
簡單來說,我們把觸摸事件分為三個(gè)過程——開始、移動(dòng)、結(jié)束,然后在這三個(gè)過程中,就可以分別實(shí)現(xiàn)對應(yīng)的邏輯與操作了:
touchmove獲取觸摸的起始點(diǎn)
touchmove計(jì)算觸摸后的偏移量
判斷偏移的方向,改變圖片位置
通過這套邏輯,我們模擬的移動(dòng)設(shè)備的觸摸效果就能成功實(shí)現(xiàn)了:
文章本身只是對項(xiàng)目整體思路和重點(diǎn)部分的講解,一些細(xì)節(jié)點(diǎn)也不可能面面俱到,還請大家對照源碼自行理解學(xué)習(xí)~
最后我想說的是,類似輪播這樣的優(yōu)秀插件其實(shí)已經(jīng)有很多了,但這并不妨礙我們寫一個(gè)自己的版本。因?yàn)橹挥凶约簩懸槐椋⒃谀X中走一遍自己的思維過程,然后在學(xué)習(xí)一些優(yōu)秀的源碼及實(shí)現(xiàn)時(shí)才不至于懵圈。
到止為止,我們第二個(gè)輪子的開發(fā)也算順利完成了,所有源碼已同步更新到github,如果大家發(fā)現(xiàn)有bug或其他問題,可以回復(fù)在項(xiàng)目的issue中,咱們后會(huì)有期!(挖坑不填,逃。。
更新(2018-8-14)已更新使用Webpack打包后的ES6版本,支持ES6模塊化引入方式。
import { Carousel } from "csdwheels" import { CarouselMobile } from "csdwheels"
具體的使用方法請參考README
To be continued...
參考內(nèi)容JS 跑馬燈(輪播圖)效果中,最后一張滾回到第一張,怎樣優(yōu)化“視覺倒退”?
用原生的javascript 實(shí)現(xiàn)一個(gè)無限滾動(dòng)的輪播圖
原生JavaScript實(shí)現(xiàn)輪播圖
原生js實(shí)現(xiàn)輪播圖
手把手原生js簡單輪播圖
vue-swiper
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96851.html
摘要:使用構(gòu)造函數(shù)那么有沒有一種辦法,可以不寫函數(shù)名,直接聲明一個(gè)函數(shù)并自動(dòng)調(diào)用它呢答案肯定的,那就是使用自執(zhí)行函數(shù)。 日常工作中經(jīng)常會(huì)發(fā)現(xiàn)有大量業(yè)務(wù)邏輯是重復(fù)的,而用別人的插件也不能完美解決一些定制化的需求,所以我決定把一些常用的組件抽離、封裝出來,形成一套自己的插件庫。同時(shí),我將用這個(gè)教程系列記錄下每一個(gè)插件的開發(fā)過程,手把手教你如何一步一步去造出一套實(shí)用性、可復(fù)用性高的輪子。 So, ...
摘要:很久沒上掘金發(fā)現(xiàn)草稿箱里存了好幾篇沒發(fā)的文章最近梳理下發(fā)出來往期面試官系列如何實(shí)現(xiàn)深克隆面試官系列的實(shí)現(xiàn)面試官系列前端路由的實(shí)現(xiàn)面試官系列基于數(shù)據(jù)劫持的雙向綁定優(yōu)勢所在面試官系列你為什么使用前端框架前言設(shè)計(jì)前端組件是最能考驗(yàn)開發(fā)者基本功的測 很久沒上掘金,發(fā)現(xiàn)草稿箱里存了好幾篇沒發(fā)的文章,最近梳理下發(fā)出來 往期 面試官系列(1): 如何實(shí)現(xiàn)深克隆 面試官系列(2): Event Bus...
摘要:模塊化是隨著前端技術(shù)的發(fā)展,前端代碼爆炸式增長后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調(diào)也不等同于異步。將會(huì)討論安全的類型檢測惰性載入函數(shù)凍結(jié)對象定時(shí)器等話題。 Vue.js 前后端同構(gòu)方案之準(zhǔn)備篇——代碼優(yōu)化 目前 Vue.js 的火爆不亞于當(dāng)初的 React,本人對寫代碼有潔癖,代碼也是藝術(shù)。此篇是準(zhǔn)備篇,工欲善其事,必先利其器。我們先在代...
摘要:前言這個(gè)輪子已經(jīng)有很多人造過了,為了不重復(fù)造輪子,我將本項(xiàng)目以三階段實(shí)現(xiàn)大家可以在中的查看純前端后端前端后端前端希望能給大家一個(gè)漸進(jìn)學(xué)習(xí)的經(jīng)驗(yàn)。 前言 Vue+Socket.io這個(gè)輪子已經(jīng)有很多人造過了,為了不重復(fù)造輪子,我將本項(xiàng)目以三階段實(shí)現(xiàn)(大家可以在github中的Releases查看): 純前端(Vuex) 后端+前端(JavaScript) 后端+前端(TypeScrip...
摘要:更新日志更新完成靜態(tài)頁面原型修復(fù)使用的正確姿勢更新添加靜態(tài)頁面更新添加使用方法請戳我主要作用就是在你開發(fā)環(huán)節(jié)在后端同學(xué)還未開發(fā)完成的情況下,提供一個(gè)。 底下評(píng)論說是標(biāo)題黨,或者是光扔個(gè)github地址上來的同學(xué)我就不說什么了。你們有看看倉庫的提交記錄么?我還沒有吃撐到開個(gè)倉庫去騙star.我的出發(fā)點(diǎn)就是每天更新一部分代碼,教大家用我所提到的技術(shù)棧搭建一個(gè)blog,我的出發(fā)點(diǎn)就是這么簡單...
閱讀 1975·2023-04-25 15:45
閱讀 1214·2021-09-29 09:34
閱讀 2503·2021-09-03 10:30
閱讀 2009·2019-08-30 15:56
閱讀 1466·2019-08-29 15:31
閱讀 1273·2019-08-29 15:29
閱讀 3204·2019-08-29 11:24
閱讀 3061·2019-08-26 13:45