摘要:沒有看過上一篇文章的話,可以在這里找到原生系列之工廠模式。那么這篇文章,我們將基于上述的,從頭開始寫一個(gè)無限循環(huán)輪播圖的組件。附無限循環(huán)輪播圖示例本文源碼
前情回顧
在上一篇文章中,我們封裝了一個(gè)DOM庫(qnode),為了讓大家直觀地感受到其方便友好的自定義工廠模式,于是給大家?guī)砹诉@篇文章。
沒有看過上一篇文章的話,可以在這里找到:原生js系列之DOM工廠模式。
那么這篇文章,我們將基于上述的qnode,從頭開始寫一個(gè)無限循環(huán)輪播圖的組件。
思路講解先看一張輪播布局圖:
滑動(dòng)的時(shí)候,整個(gè)輪播容器整體前進(jìn)或后退一格,通過css3過渡效果的設(shè)置,來達(dá)到滑動(dòng)的效果。也許你會(huì)疑惑,頭尾怎么會(huì)多出兩張圖呢?
其實(shí)無限循環(huán)輪播的核心就在于頭尾多出的兩張圖,從圖三再向后滑動(dòng),會(huì)滑到紅色圖一(我稱之為占位圖一),這個(gè)時(shí)候給用戶的感覺就是無縫從最后一張滑動(dòng)到第一張的,當(dāng)他滑到占位圖一時(shí),我們再瞬間切換到粉色圖一(即真正的圖一),由于是瞬間變換,用戶是感知不到的。同理,從圖一滑到圖三也一樣。由此,周而復(fù)始,無窮無盡,給人的感覺是永遠(yuǎn)也不會(huì)到盡頭,當(dāng)然個(gè)中奧妙只有我們知道哈哈。
目錄結(jié)構(gòu)swiper ├── README.md ├── index.js ├── qnode │?? ├── index.js │?? ├── method.js │?? └── store.js ├── render │?? ├── index.js │?? ├── indicator.js │?? └── list.js └── styles ├── indicator.mcss ├── list.mcss └── wrap.mcss
說明:mcss文件是通過css-modules來編譯的,給class名稱生成唯一標(biāo)識(shí),防止命名沖突。這里有我配置好的一套腳手架,覺得webpack配置麻煩的話,可以clone我這個(gè)項(xiàng)目來編譯代碼:webpack-build。
代碼編寫index.js
import qnode from "./qnode" import render from "./render" const defaults = { initIndex: 1, autoplay: { use: true, delay: 3000 }, slide: { use: true, scale: 1 / 3, speed: 0.2 }, indicator: { use: true, bottom: "", dotClass: "", dotActiveClass: "" } } export default function swiper (node, { datas, initIndex, slide, autoplay, indicator }) { if (!node || !datas || !datas.length) return // 儲(chǔ)存數(shù)據(jù)的前后順序很重要,一定要在調(diào)用前設(shè)置 qnode.setStore("datas", datas) qnode.setStore("index", (initIndex || defaults.initIndex) - 1) qnode.setStore("slide", Object.assign({}, defaults.slide, slide)) qnode.setStore("autoplay", Object.assign({}, defaults.autoplay, autoplay)) qnode.setStore("indicator", Object.assign({}, defaults.indicator, indicator)) // 渲染dom并儲(chǔ)存在qnode,以便后續(xù)的獲取和操作 render() // 自動(dòng)輪播 qnode.execMethod("autoplay") // 滑動(dòng)翻頁 qnode.execMethod("slide") // 掛載到真實(shí)的節(jié)點(diǎn)上 qnode.getNode("wrap").appendTo(node) }
render/index.js
import qnode from "../qnode" import renderList from "./list" import renderIndicator from "./indicator" import mcss from "../styles/wrap.mcss" export default function () { renderList() // 渲染列表 renderIndicator() // 渲染指示器,若沒有開啟則不會(huì)渲染 qnode.setNode("wrap", "$div") .addClass(mcss.wrap) .append([ qnode.getNode("list"), qnode.getNode("indicator") // 有可能沒有值,這一層我們的qnode會(huì)過濾調(diào),所以放心大膽地寫 ]) }
render/list.js
import { isElement, isString } from "@m/utils/is" import qnode from "../qnode" import mcss from "../styles/list.mcss" function getItemNode (data) { const qItem = qnode.q("$div").addClass(mcss.item) if (isElement(data)) { return qItem.append(data) } if (isString(data)) { return qItem.html(data) } return qItem.html(` `) } export default function () { const datas = qnode.getStore("datas") const tdTime = qnode.getStore("tdTime") const posIndex = qnode.getStore("index") + 1 const qItems = datas.map(item => getItemNode(item)) // 首位多插入一個(gè)節(jié)點(diǎn),用于視覺感知,交互完成后瞬間替換到相應(yīng)的節(jié)點(diǎn) qItems.unshift(getItemNode(datas[datas.length - 1])) qItems.push(getItemNode(datas[0])) qnode.setNode("list", "$div") .addClass(mcss.list) .style({ transitionDuration: tdTime + "ms", transform: `translateX(${posIndex * -100}%)` }) .append(qItems) }
render/indicator.js
import qnode from "../qnode" import mcss from "../styles/indicator.mcss" export default function () { const indicator = qnode.getStore("indicator") const last = qnode.getStore("datas").length - 1 const index = qnode.getStore("index") const dotClass = indicator.dotClass || mcss.dot const dotActiveClass = indicator.dotActiveClass || mcss.dotActive if (indicator.use) { let qDots = [] for (let i = 0; i <= last; i++) { qDots.push( qnode.q("$div").addClass(dotClass, (i === index) && dotActiveClass) ) } qnode.setNode("dots", qDots) qnode.setStore("dotActiveClass", dotActiveClass) qnode.setNode("indicator", "$div") .addClass(mcss.indicator) .style("bottom", indicator.bottom) .append(qDots) } }
qnode/index.js
import { QNode } from "@m/qnode" import { tdTime } from "./store" import { change, autoplay, slide, indicator } from "./method" const qnode = new QNode() qnode.setStore("tdTime", tdTime) qnode.setMethod("change", change) qnode.setMethod("autoplay", autoplay) qnode.setMethod("slide", slide) qnode.setMethod("indicator", indicator) export default qnode
qnode/store.js
// 靜態(tài)數(shù)據(jù)可以放在這里 export const tdTime = 500
qnode/method.js
import touchSlide from "./touchSlide" // 翻頁處理 export function change (isNext) { let index = this.getStore("index") let cacheIndex = index // 用于記錄上一次的索引,移除指示器激活樣式時(shí)使用 let last = this.getStore("datas").length - 1 let tdTime = this.getStore("tdTime") let qList = this.getNode("list") let isNextContinue = isNext && (index === last) let isPrevContinue = !isNext && (index === 0) let posIndex = index + (isNext ? 2 : 0) if (isNextContinue || isPrevContinue) { // 滑到占位圖 qList.style("transform", `translateX(${posIndex * -100}%)`) index = isNextContinue ? 0 : last setTimeout(() => { qList.style({ transitionDuration: "0ms", transform: `translateX(${(index + 1) * -100}%)` }) }, tdTime) } else { qList.style({ transitionDuration: tdTime + "ms", transform: `translateX(${posIndex * -100}%)` }) index += isNext ? 1 : -1 } this.setStore("index", index) this.execMethod("indicator", cacheIndex, index) } // 自動(dòng)輪播 export function autoplay () { let opt = this.getStore("autoplay") if (!opt.use) return let timer = setInterval(() => { this.execMethod("change", true) }, opt.delay) this.setStore("timer", timer) } // 滑動(dòng)處理 export function slide () { let qWrap = this.getNode("wrap") let qList = this.getNode("list") let tdTime = this.getStore("tdTime") let slideData = this.getStore("slide") let self = this if (!slideData.use) return touchSlide(qWrap.current(), { delay: 0, start () { // 清除輪播定時(shí)器和css3過渡效果 clearTimeout(self.getStore("timer")) qList.style("transitionDuration", "0ms") }, move (info) { let posIndex = self.getStore("index") + 1 let move = info.disX / qWrap.width() * 100 let total = posIndex * -100 + move qList.style("transform", `translateX(${total}%)`) }, end (info) { // 開啟輪播和css3過渡效果 self.execMethod("autoplay") qList.style("transitionDuration", tdTime + "ms") let posIndex = self.getStore("index") + 1 let scale = Math.abs(info.disX) / qWrap.width() let speed = Math.abs(info.speedX) if (scale >= slideData.scale || speed >= slideData.speed) { self.execMethod("change", info.disX < 0) // 翻頁 } else { qList.style("transform", `translateX(${posIndex * -100}%)`) } } }) } // 修改指示器索引 export function indicator (lastIndex, currIndex) { const qDots = this.getNode("dots") const dotActiveClass = this.getStore("dotActiveClass") if (qDots && dotActiveClass) { qDots[lastIndex].removeClass(dotActiveClass) qDots[currIndex].addClass(dotActiveClass) } }
touchSlide.js
// 截流 function throttle (fn, delay = 100) { let wait = false return function () { if (!wait) { fn && fn.apply(this, arguments) wait = true setTimeout(() => { wait = false }, delay) } } } /** * * 滑動(dòng) * @param {HTMLElement} node * @param {Object} { * delay = 100, // move截流時(shí)間 * start, // 滑動(dòng)開始 * 參數(shù): pageX, pageY * move, // 滑動(dòng)中,會(huì)不斷地觸發(fā),可以通過截流來限制觸發(fā)頻率 * 參數(shù): time, // 總時(shí)間:ms disX, // 總路程:px disY, addX, // 路程增量:px addY, speedX: disX / time, // 平均速度:px/ms speedY: disY / time * end, // 滑動(dòng)結(jié)束,參數(shù)同move * } */ export default function (node, { delay = 100, start, move, end }) { if (!node) return let sTouch, eTouch, sTime let touch, time, disX, disY, addX, addY node.addEventListener("touchstart", e => { e.preventDefault() sTime = e.timeStamp sTouch = eTouch = e.targetTouches[0] start && start({ pageX: sTouch.pageX, pageY: sTouch.pageY }) }, false) node.addEventListener("touchmove", throttle(e => { touch = e.targetTouches[0] time = e.timeStamp - sTime disX = touch.pageX - sTouch.pageX disY = touch.pageY - sTouch.pageY addX = touch.pageX - eTouch.pageX addY = touch.pageY - eTouch.pageY move && move({ time, // 總時(shí)間:ms disX, // 總路程:px disY, addX, // 路程增量:px addY, speedX: disX / time, // 平均速度:px/ms speedY: disY / time }) // 記錄上一次touch eTouch = touch }, delay), false) node.addEventListener("touchend", e => { touch = e.changedTouches[0] time = e.timeStamp - sTime disX = touch.pageX - sTouch.pageX disY = touch.pageY - sTouch.pageY addX = touch.pageX - eTouch.pageX addY = touch.pageY - eTouch.pageY end && end({ time, disX, disY, addX, addY, speedX: disX / time, speedY: disY / time }) }, false) }
styles/wrap.mcss
.wrap { position: relative; overflow: hidden; transform: translate3d(0, 0, 0); }
styles/list.mcss
.list { display: flex; flex-direction: row; transform: translateX(0); transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .item { flex-basis: 100%; flex-shrink: 0; box-sizing: border-box; a { display: block; font-size: 0; img { width: 100%; height: auto; } } }
styles/indicator.mcss
.indicator { position: absolute; bottom: 1em; left: 0; right: 0; display: flex; justify-content: center; } .dot { width: 1em; height: 0.12em; margin: 0 0.12em; background-color: rgba(255, 255, 255, 0.5); &-active { background-color: #fff; } }README 參數(shù)
node: 要掛載的dom節(jié)點(diǎn),必須
options: 如下(其中datas是必要的)
{ initIndex: 1, // 初始化展示的索引 autoplay: { // 自動(dòng)輪播設(shè)置 use: true, // 開關(guān) delay: 3000 // 間隔3s }, slide: { // 手指滑動(dòng)設(shè)置 use: true, // 開關(guān) scale: 1/3, // 劃過總共寬度的1/3則翻頁 speed: 0.2 // 滑動(dòng)的速度超過0.2px/ms則翻頁,即快速滑動(dòng)也可以翻頁 }, indicator: { // 索引指示器設(shè)置 use: true, // 開關(guān) bottom: "", // 底部的距離 dotClass: "", // 自定義圓點(diǎn)樣式 dotActiveClass: "" // 自定義激活樣式 }, datas: [ // 圖片數(shù)據(jù) { src: "xxx", // 圖片URL href: "/", // 圖片錨點(diǎn),可以不設(shè)置 target: "_blank" // 點(diǎn)擊錨點(diǎn)的跳轉(zhuǎn)處理(是在當(dāng)前頁打開還是新建窗口) } ] }示例
import swiper from "@c/swiper" import img1 from "./images/1.jpg" import img2 from "./images/2.jpg" import img3 from "./images/3.jpg" import img4 from "./images/4.jpg" import img5 from "./images/5.jpg" import img6 from "./images/6.jpg" const rootNode = document.getElementById("root") swiper(rootNode, { // initIndex: 1, // autoplay: { // use: true, // delay: 3000 // }, // slide: { // use: true, // scale: 1/3, // speed: 0.2 // }, // indicator: { // use: true, // bottom: "", // dotClass: "", // dotActiveClass: "" // }, datas: [ { src: img1, href: "/", target: "_blank" }, { src: img2, href: "/", target: "_blank" }, { src: img3, href: "/", target: "_blank" }, { src: img4, href: "/", target: "_blank" }, { src: img5, href: "/", target: "_blank" }, { src: img6, href: "/", target: "_blank" } ] })使用心得
總體來說使用qnode來開發(fā)的話還是比較方便的,文件拆分以及數(shù)據(jù)共享都可以做到,唯一有一點(diǎn)瑕疵的話,就是對于js執(zhí)行的順序要慎重考慮。想一想為什么render文件暴露出來的是函數(shù),原因就是因?yàn)榇藭r(shí)數(shù)據(jù)還未儲(chǔ)存到qnode,因此通過函數(shù)來進(jìn)行惰性加載,在合適的地方執(zhí)行。
對于qnode,目前還沒有錯(cuò)誤提醒,調(diào)用方式不對的話沒有信息吐出,后續(xù)可以考慮補(bǔ)上這個(gè)功能,畢竟其他開發(fā)者用的話,可能并不熟悉API,調(diào)用姿勢不對也是有可能發(fā)生的。
以上就是本文的全部內(nèi)容了。
附:
無限循環(huán)輪播圖示例
本文源碼
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/112873.html
摘要:沒有看過上一篇文章的話,可以在這里找到原生系列之工廠模式。那么這篇文章,我們將基于上述的,從頭開始寫一個(gè)無限循環(huán)輪播圖的組件。附無限循環(huán)輪播圖示例本文源碼 前情回顧 在上一篇文章中,我們封裝了一個(gè)DOM庫(qnode),為了讓大家直觀地感受到其方便友好的自定義工廠模式,于是給大家?guī)砹诉@篇文章。 沒有看過上一篇文章的話,可以在這里找到:原生js系列之DOM工廠模式。 那么這篇文章,我們...
摘要:沒有看過上一篇文章的話,可以在這里找到原生系列之工廠模式。那么這篇文章,我們將基于上述的,從頭開始寫一個(gè)無限循環(huán)輪播圖的組件。附無限循環(huán)輪播圖示例本文源碼 前情回顧 在上一篇文章中,我們封裝了一個(gè)DOM庫(qnode),為了讓大家直觀地感受到其方便友好的自定義工廠模式,于是給大家?guī)砹诉@篇文章。 沒有看過上一篇文章的話,可以在這里找到:原生js系列之DOM工廠模式。 那么這篇文章,我們...
摘要:實(shí)現(xiàn)一個(gè)非無限循環(huán)不自動(dòng)切換的輪播圖只需要幾張圖片和兩個(gè)按鈕簡化部分兩個(gè)按鈕,幾張圖片假如有四張圖右側(cè)按鈕左側(cè)按鈕部分動(dòng)態(tài)添加刪除的屬性部分已是最后一張圖這是第一張圖 實(shí)現(xiàn)一個(gè)非無限循環(huán)不自動(dòng)切換的輪播圖只需要幾張圖片和兩個(gè)按鈕(簡化) HTML部分 兩個(gè)按鈕,幾張圖片(假如有四張圖) 右側(cè)按鈕 左側(cè)按鈕 CSS部分 動(dòng)態(tài)...
摘要:對,滑動(dòng)式幻燈片的關(guān)鍵就在于隱藏。在條件里我們添加一個(gè)事件相當(dāng)于滑動(dòng)后的回掉,依賴這個(gè)事件在幻燈片滑動(dòng)執(zhí)行完畢后立即執(zhí)行里面的閃回操作。通過添加事件監(jiān)聽滑動(dòng)是否結(jié)束從而迅速閃回,達(dá)到貌似無限滑動(dòng)的效果。 slider輪播組件,在各類網(wǎng)站上出現(xiàn)及其頻繁,有漸隱式的,滑動(dòng)式的等等一系列。栗子在這: 但我當(dāng)初學(xué)習(xí)寫輪播時(shí)卻被各種入門教程搞得焦頭爛額。不是代碼太復(fù)雜,就是封裝太嚴(yán)重,初學(xué)者很難...
摘要:于是在不斷的摸索和思考中,想出了工廠模式這個(gè)概念,咱們可以這么理解工廠就是取快遞的地方,不管是從哪里發(fā)來的貨品,統(tǒng)一送到這里,然后再由特定的人群來取。 寫在前面 如今,在項(xiàng)目中使用React、Vue等框架作為技術(shù)棧已成為一種常態(tài),在享受帶來便利性的同時(shí),也許我們漸漸地遺忘原生js的寫法。 現(xiàn)在,是時(shí)候回歸本源,響應(yīng)原始的召喚了。本文將一步一步帶領(lǐng)大家封裝一套屬于自己的DOM操作庫,我將...
閱讀 3691·2021-11-19 09:56
閱讀 1473·2021-09-22 15:11
閱讀 1133·2019-08-30 15:55
閱讀 3379·2019-08-29 14:02
閱讀 2917·2019-08-29 11:07
閱讀 439·2019-08-28 17:52
閱讀 3174·2019-08-26 13:59
閱讀 442·2019-08-26 13:53