摘要:沒有看過上一篇文章的話,可以在這里找到原生系列之工廠模式。那么這篇文章,我們將基于上述的,從頭開始寫一個無限循環輪播圖的組件。附無限循環輪播圖示例本文源碼
前情回顧
在上一篇文章中,我們封裝了一個DOM庫(qnode),為了讓大家直觀地感受到其方便友好的自定義工廠模式,于是給大家帶來了這篇文章。
沒有看過上一篇文章的話,可以在這里找到:原生js系列之DOM工廠模式。
那么這篇文章,我們將基于上述的qnode,從頭開始寫一個無限循環輪播圖的組件。
思路講解先看一張輪播布局圖:
滑動的時候,整個輪播容器整體前進或后退一格,通過css3過渡效果的設置,來達到滑動的效果。也許你會疑惑,頭尾怎么會多出兩張圖呢?
其實無限循環輪播的核心就在于頭尾多出的兩張圖,從圖三再向后滑動,會滑到紅色圖一(我稱之為占位圖一),這個時候給用戶的感覺就是無縫從最后一張滑動到第一張的,當他滑到占位圖一時,我們再瞬間切換到粉色圖一(即真正的圖一),由于是瞬間變換,用戶是感知不到的。同理,從圖一滑到圖三也一樣。由此,周而復始,無窮無盡,給人的感覺是永遠也不會到盡頭,當然個中奧妙只有我們知道哈哈。
目錄結構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名稱生成唯一標識,防止命名沖突。這里有我配置好的一套腳手架,覺得webpack配置麻煩的話,可以clone我這個項目來編譯代碼: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 // 儲存數據的前后順序很重要,一定要在調用前設置 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并儲存在qnode,以便后續的獲取和操作 render() // 自動輪播 qnode.execMethod("autoplay") // 滑動翻頁 qnode.execMethod("slide") // 掛載到真實的節點上 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() // 渲染指示器,若沒有開啟則不會渲染 qnode.setNode("wrap", "$div") .addClass(mcss.wrap) .append([ qnode.getNode("list"), qnode.getNode("indicator") // 有可能沒有值,這一層我們的qnode會過濾調,所以放心大膽地寫 ]) }
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)) // 首位多插入一個節點,用于視覺感知,交互完成后瞬間替換到相應的節點 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
// 靜態數據可以放在這里 export const tdTime = 500
qnode/method.js
import touchSlide from "./touchSlide" // 翻頁處理 export function change (isNext) { let index = this.getStore("index") let cacheIndex = index // 用于記錄上一次的索引,移除指示器激活樣式時使用 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) } // 自動輪播 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) } // 滑動處理 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 () { // 清除輪播定時器和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) } } } /** * * 滑動 * @param {HTMLElement} node * @param {Object} { * delay = 100, // move截流時間 * start, // 滑動開始 * 參數: pageX, pageY * move, // 滑動中,會不斷地觸發,可以通過截流來限制觸發頻率 * 參數: time, // 總時間:ms disX, // 總路程:px disY, addX, // 路程增量:px addY, speedX: disX / time, // 平均速度:px/ms speedY: disY / time * end, // 滑動結束,參數同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, // 總時間: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 參數
node: 要掛載的dom節點,必須
options: 如下(其中datas是必要的)
{ initIndex: 1, // 初始化展示的索引 autoplay: { // 自動輪播設置 use: true, // 開關 delay: 3000 // 間隔3s }, slide: { // 手指滑動設置 use: true, // 開關 scale: 1/3, // 劃過總共寬度的1/3則翻頁 speed: 0.2 // 滑動的速度超過0.2px/ms則翻頁,即快速滑動也可以翻頁 }, indicator: { // 索引指示器設置 use: true, // 開關 bottom: "", // 底部的距離 dotClass: "", // 自定義圓點樣式 dotActiveClass: "" // 自定義激活樣式 }, datas: [ // 圖片數據 { src: "xxx", // 圖片URL href: "/", // 圖片錨點,可以不設置 target: "_blank" // 點擊錨點的跳轉處理(是在當前頁打開還是新建窗口) } ] }示例
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來開發的話還是比較方便的,文件拆分以及數據共享都可以做到,唯一有一點瑕疵的話,就是對于js執行的順序要慎重考慮。想一想為什么render文件暴露出來的是函數,原因就是因為此時數據還未儲存到qnode,因此通過函數來進行惰性加載,在合適的地方執行。
對于qnode,目前還沒有錯誤提醒,調用方式不對的話沒有信息吐出,后續可以考慮補上這個功能,畢竟其他開發者用的話,可能并不熟悉API,調用姿勢不對也是有可能發生的。
以上就是本文的全部內容了。
附:
無限循環輪播圖示例
本文源碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92137.html
摘要:沒有看過上一篇文章的話,可以在這里找到原生系列之工廠模式。那么這篇文章,我們將基于上述的,從頭開始寫一個無限循環輪播圖的組件。附無限循環輪播圖示例本文源碼 前情回顧 在上一篇文章中,我們封裝了一個DOM庫(qnode),為了讓大家直觀地感受到其方便友好的自定義工廠模式,于是給大家帶來了這篇文章。 沒有看過上一篇文章的話,可以在這里找到:原生js系列之DOM工廠模式。 那么這篇文章,我們...
摘要:沒有看過上一篇文章的話,可以在這里找到原生系列之工廠模式。那么這篇文章,我們將基于上述的,從頭開始寫一個無限循環輪播圖的組件。附無限循環輪播圖示例本文源碼 前情回顧 在上一篇文章中,我們封裝了一個DOM庫(qnode),為了讓大家直觀地感受到其方便友好的自定義工廠模式,于是給大家帶來了這篇文章。 沒有看過上一篇文章的話,可以在這里找到:原生js系列之DOM工廠模式。 那么這篇文章,我們...
摘要:實現一個非無限循環不自動切換的輪播圖只需要幾張圖片和兩個按鈕簡化部分兩個按鈕,幾張圖片假如有四張圖右側按鈕左側按鈕部分動態添加刪除的屬性部分已是最后一張圖這是第一張圖 實現一個非無限循環不自動切換的輪播圖只需要幾張圖片和兩個按鈕(簡化) HTML部分 兩個按鈕,幾張圖片(假如有四張圖) 右側按鈕 左側按鈕 CSS部分 動態...
摘要:對,滑動式幻燈片的關鍵就在于隱藏。在條件里我們添加一個事件相當于滑動后的回掉,依賴這個事件在幻燈片滑動執行完畢后立即執行里面的閃回操作。通過添加事件監聽滑動是否結束從而迅速閃回,達到貌似無限滑動的效果。 slider輪播組件,在各類網站上出現及其頻繁,有漸隱式的,滑動式的等等一系列。栗子在這: 但我當初學習寫輪播時卻被各種入門教程搞得焦頭爛額。不是代碼太復雜,就是封裝太嚴重,初學者很難...
摘要:于是在不斷的摸索和思考中,想出了工廠模式這個概念,咱們可以這么理解工廠就是取快遞的地方,不管是從哪里發來的貨品,統一送到這里,然后再由特定的人群來取。 寫在前面 如今,在項目中使用React、Vue等框架作為技術棧已成為一種常態,在享受帶來便利性的同時,也許我們漸漸地遺忘原生js的寫法。 現在,是時候回歸本源,響應原始的召喚了。本文將一步一步帶領大家封裝一套屬于自己的DOM操作庫,我將...
閱讀 693·2023-04-25 19:53
閱讀 4274·2021-09-22 15:13
閱讀 2568·2019-08-30 10:56
閱讀 1324·2019-08-29 16:27
閱讀 2938·2019-08-29 14:00
閱讀 2413·2019-08-26 13:56
閱讀 438·2019-08-26 13:29
閱讀 1616·2019-08-26 11:31