# 🏅 Day 22 - Swiper 輪播套件運用 ## 今日學習目標 - 學習在 Nuxt3 使用 Swiper.js 輪播套件 - 學習將 Swiper 封裝成元件,並透過 props 傳遞輪播功能的參數至元件中 ## 安裝 Swiper 開啟終端機,輸入以下指令安裝 Swiper v11.1.14 : ```bash npm install swiper@11.1.14 ``` ## 匯入 Swiper 的功能 安裝完成後,進入元件中匯入 Swiper 的核心 JS 、CSS 與模組 。Swiper 核心不包含模組功能,因此像是 Autoplay(自動播放)、Pagination(頁碼)、Navigation(導航)等功能需要額外匯入。以下示範於 `/pages/index.vue` 進行匯入 : ```html <!-- /pages/index.vue --> <script setup> // Swiper JS 核心功能 import Swiper from "swiper"; // Swiper 模組 import { Navigation, Pagination, Autoplay } from "swiper/modules"; // Swiper 核心與模組的樣式 import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; </script> <template> <!-- Swiper 的模板結構 --> </template> ``` ## 基礎使用方式 第一種方法是在元件中直接使用 Swiper 的 HTML 結構,接下來將基於上方的匯入設定在 `/pages/index.vue` 進行輪播設定。 ### 步驟一. 撰寫 HTML 結構 在最外層用一個 `div.swiper` 作為整個 Swiper 的容器,並將所有輪播內容(即 `div.swiper-slide`)放入 `div.swiper-wrapper`。除此之外,在匯入 Navigation 和 Pagination 模組後,還需額外加入觸發這些功能的 HTML 元素。 例如下方範例 : ```html <!-- /pages/index.vue --> <template> <!-- Swiper 的容器 --> <div class="swiper"> <!-- 放置所有的輪播內容 --> <div class="swiper-wrapper"> <!-- 每個 swiper-slide 都是一個輪播內容 --> <div class="swiper-slide"> <img src="https://images.unsplash.com/photo-1636909798741-42d59f0265b0?q=80&w=900&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="圖片一" /> </div> <div class="swiper-slide"> <img src="https://images.unsplash.com/photo-1516007146048-bfcffccbc95c?q=80&w=900&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="圖片二" /> </div> <div class="swiper-slide"> <img src="https://images.unsplash.com/photo-1516007112993-555f24caca6d?q=80&w=900&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="圖片三" /> </div> </div> <!-- 有使用 Pagination 模組必需要加入 --> <div class="swiper-pagination"></div> <!-- 有使用 Navigation 模組必需要加入 --> <div class="swiper-button-prev"></div> <div class="swiper-button-next"></div> </div> </template> ``` ### 步驟二. 選擇性調整 Swiper 樣式 如果需要調整 Swiper 的樣式,可以在 `<style>` 區塊內覆蓋預設樣式,例如修改 Swiper 容器的寬度: ```jsx // /pages/index.vue <style scoped> .swiper { max-width: 900px; } </style> ``` ### 步驟三. 初始化 Swiper JS 首先,定義 `swiperRef` 變數,並在 `<div class="swiper">` 加入 `ref="swiperRef"`,取得該元素的 DOM 節點。 ```jsx // /pages/index.vue <script setup> const swiperRef = ref(null); </script> <template> <div class="swiper" ref="swiperRef" > <!-- 輪播內容 --> </div> </template> ``` 接著在 `onMounted()` 生命週期透過 `new Swiper()` 來初始化 Swiper ,需傳入兩個參數 : - 第一個參數 : 傳入要套用輪播效果的容器,可使用 CSS 選擇器或是 HTML 元素的節點。這邊使用 `swiperRef.value` 取得的 HTML 元素。 ```jsx // /pages/index.vue <script setup> import Swiper from "swiper"; import { Navigation, Pagination, Autoplay } from "swiper/modules"; import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; const swiperRef = ref(null); onMounted(() => { new Swiper(swiperRef.value, { /*...*/ }) }); </script> <template> <div class="swiper" ref="swiperRef" > <!-- 輪播內容 --> </div> </template> ``` - 第二個參數 : 是一個物件,用來定義 Swiper 的行為和外觀。詳細設定與調整方法可以閱讀 [官方文件](https://swiperjs.com/swiper-api#parameters) ,以下是在範例中使用的屬性設定 : - `loop` : 循環模式,設定成 `true` 允許播放至最後一個輪播時可以取得第一個輪播繼續播放。 - `slidesPerView` : 每次顯示的輪播 ( swiper-slide ) 數量。 - `breakpoints` : 設定不同斷點輪播顯示的數量以及輪播之間的距離。 - `pagination` : 設定頁碼的點擊操作的元素 ( `el` 屬性 ) 以及頁碼的外觀 ( `type` 屬性 )。 - `navigation` : 設定導航按鈕,例如切換上一個 ( `nextEl`屬性 ) 與下一個 ( `prevEl` 屬性 ) 點擊操作的元素。 - `autoplay`: 自動輪播,在此設定 1000 毫秒作為 swiper-slide 轉換的延遲 ( delay 屬性 ) 。 - `modules` : 帶入輪播需要用到的模組。 ```html <script setup> import Swiper from "swiper"; import { Navigation, Pagination, Autoplay } from "swiper/modules"; import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; const swiperRef = ref(null); onMounted(() => { new Swiper(swiperRef.value, { loop: true, // 開啟循環模式 slidesPerView: 1, // 預設每次顯示 1 個輪播 breakpoints: { // 992px 以上套用響應式斷點設定 992: { slidesPerView: 2, // 在 992px 以上顯示 2 個輪播 spaceBetween: 20, // 輪播之間的間距為 20px }, }, // 設定頁碼模組的功能 ( 需要搭配 Pagination 模組使用 ) pagination: { el: ".swiper-pagination", // 頁碼操作的元素 type: "bullets", // 頁碼的外觀 }, // 設定導航模組的功能 ( 需要搭配 Navigation 模組使用 ) navigation: { nextEl: ".swiper-button-next", // 下一個按鈕操作的元素 prevEl: ".swiper-button-prev", // 上一個按鈕操作的元素 }, autoplay: { delay: 1000 }, // 自動輪播,延遲 1000 毫秒 modules: [Navigation, Pagination, Autoplay], // 載入輪播需要用到的模組 }); }); </script> <template> <div class="swiper" ref="swiperRef" > <!-- 輪播內容 --> </div> </template> ``` #### 注意 : `new Swiper()` 必需在 `onMounted()` 生命週期定義。因為 `swiperRef` 變數在伺服器端無法取得 DOM 節點,會造成 Swiper 無法正確初始化,所以需要移動到客戶端執行 `onMounted()` 生命週期。 ```html <!-- 錯誤示範 : --> <script setup> import Swiper from "swiper"; import { Navigation, Pagination, Autoplay } from "swiper/modules"; import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; const swiperRef = ref(null); new Swiper(swiperRef.value, { loop: true, slidesPerView: 1, breakpoints: { 992: { slidesPerView: 2, spaceBetween: 20, }, }, pagination: { el: ".swiper-pagination", type: "bullets", }, navigation: { nextEl: ".swiper-button-next", prevEl: ".swiper-button-prev", }, autoplay: { delay: 1000 }, modules: [Navigation, Pagination, Autoplay], }); </script> ``` ## 封裝成元件進行複用 第二種方法是將 Swiper 封裝成元件,透過 `props` 將輪播的參數傳遞至元件內。這樣的做法有助於元件複用,減少重複程式碼,並提升輪播功能的可維護性與可擴充性。未來如需要調整輪播效果,只需修改元件本身,不必在多個地方進行修改。接下來,我們將根據前面的基礎用法,將 Swiper 進行元件化封裝。 ### 步驟一. 定義元件檔案並規劃用途 首先,建立 `components/swiper` 資料夾,並在該資料夾下新增 `Swiper.vue` 和 `SwiperSlide.vue`。`Swiper.vue` 是放置輪播結構的主元件,用於初始化並接收外部傳入的 `props`;而 `SwiperSlide.vue` 則是用來放置每個輪播項目(即 `.swiper-slide` 的內容)。 在 `Swiper.vue` 將會透過 [插槽 Slots](https://cn.vuejs.org/guide/components/slots) 來渲染傳入的 `SwiperSlide` 的內容。最終結構如下: ```html <!-- /components/swiper/Swiper.vue --> <Swiper> <!-- 透過插槽傳遞 SwiperSlide 元件的內容 --> <SwiperSlide> <!-- 輪播內容 --> </SwiperSlide> </Swiper> ``` ### 步驟二. 設定元件的模板結構 在 `Swiper.vue` 中定義輪播的模板結構。於模板最外層加入 `div.swiper` 作為 Swiper 的容器,並包含頁碼和導航按鈕。`div.swiper-wrapper` 中的輪播內容將使用 `<slot />` 插槽來渲染傳入的內容。將來 Swiper 元件嵌套的內容會在 `<slot />` 元件的位置被渲染。 ```html <!-- /components/swiper/Swiper.vue --> <template> <div class="swiper"> <div class="swiper-wrapper"> <slot /> </div> <div class="swiper-pagination"></div> <div class="swiper-button-next"></div> <div class="swiper-button-prev"></div> </div> </template> ``` 接下來是定義 `SwiperSlide.vue` 的模板。該元件的用途是顯示每一個輪播項目的內容,模板中僅需加入 `div.swiper-slide`,並使用 `<slot />` 來渲染嵌套在 `SwiperSlide` 標籤內的內容: ```html <!-- /components/swiper/SwiperSlide.vue --> <template> <div class="swiper-slide"> <slot /> </div> </template> ``` ```html <!-- 預計會這樣使用 : --> <SwiperSlide> 輪播的內容 <!-- 此處內容會被傳遞至 SwiperSlide.vue 的 <slot /> 渲染 --> </SwiperSlide> ``` ### 步驟三. 定義 Swiper.vue 的 props 在 `Swiper.vue` 中使用 defineProps() 設定以下 `props` 接受來自父元件的參數 : ```jsx // /components/swiper/Swiper.vue <script setup> const props = defineProps({ autoplay: Object, pagination: Object, navigation: Object, loop: Boolean, slidesPerView: Number, modules: Array, breakpoints: Object, }); </script> ``` ### 步驟四. 初始化 Swiper 在 `Swiper.vue` 中設定 `swiperRef` 變數,並使用 `ref="swiperRef"` 取得輪播容器的 DOM 節點。在 `onMounted()` 生命週期把 DOM 節點與元件的 props 傳入 `new Swiper()`。 ```html <script setup> // Swiper JS 核心功能 import Swiper from "swiper"; // Swiper 核心與模組的樣式 import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; const props = defineProps({ autoplay: Object, pagination: Object, navigation: Object, loop: Boolean, slidesPerView: Number, modules: Array, breakpoints: Object, }); const swiperRef = ref(null); onMounted(() => { new Swiper(swiperRef.value, props); }); </script> <template> <div class="swiper" ref="swiperRef"> <div class="swiper-wrapper"> <slot /> </div> <div class="swiper-pagination"></div> <div class="swiper-button-next"></div> <div class="swiper-button-prev"></div> </div> </template> ``` ### 步驟五. 使用封裝好的 Swiper 元件 最後,在頁面元件中使用 `<Swiper>` 元件,將所需的輪播參數透過 `props` 傳入元件。 同時在內層使用 `SwiperSlide` 元件來呈現每個輪播項目。 ```html <script setup> // Swiper 模組 // 因為不一定每個 Swiper 使用的模組都一樣,所以改成用 props 傳入特定的模組 import { Navigation, Pagination, Autoplay } from "swiper/modules"; </script> <template> <Swiper :loop="true" :slides-per-view="1" :breakpoints="{ 992: { slidesPerView: 2, spaceBetween: 20, }, }" :pagination="{ el: '.swiper-pagination', type: 'bullets' }" :navigation="{ nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }" :autoplay="{ delay: 1000 }" :modules="[Navigation, Pagination, Autoplay]" > <!-- 內層所有的內容將會渲染至 div.swiper-wrapper內部的 <slot /> --> <SwiperSlide> <img src="https://images.unsplash.com/photo-1636909798741-42d59f0265b0?q=80&w=900&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="圖片一" /> </SwiperSlide> <SwiperSlide> <!-- SwiperSlide 內的 img 標籤將會渲染至 div.swiper-slide 內部的 <slot /> --> <img src="https://images.unsplash.com/photo-1516007146048-bfcffccbc95c?q=80&w=900&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="圖片二" /> </SwiperSlide> <SwiperSlide> <img src="https://images.unsplash.com/photo-1516007112993-555f24caca6d?q=80&w=900&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="圖片三" /> </SwiperSlide> </Swiper> </template> ``` <br> > 今日學習的[範例 Code - 資料夾: day22-swiper-example](https://github.com/hexschool/nuxt-daily-tasks-2024) ## 題目 請 fork 這一份 [模板](https://github.com/jasonlu0525/nuxt3-live-question/tree/day22-swiper) ,完成以下條件 : - 在 `/pages/index.vue` 中,已使用 `useFetch()` 取得房型列表並透過 `v-for` 渲染。然而在模板第 17 ~ 19 行的 `imageUrlList` 缺少了呈現房型多圖的輪播功能,請使用 Swiper.js 來完成圖片輪播功能 ( Swiper.js 已包含在模板內,npm install 安裝後即可使用 ) 。 - 輪播需要包含以下功能 : - 需要使用頁碼 ( pagination ) 、導航 ( navigation ) 與自動撥放功能。 - 頁碼 : 使用 bullets 的外觀,並能透過點擊頁碼切換輪播項目。 - 導航: 可以通過前後按鈕導航輪播項目。 - 自動撥放功能的延遲時間為 5 秒。 - 允許輪播項目循環播放。 - 每次顯示 1 個輪播項目。 - 補充,模板有預先在 `plugins/iconify.js` 載入 [iconify/vue](https://iconify.design/) 套件,透過全域元件的方式呈現房型資訊區塊的 icon。 `/pages/index.vue` 可以使用 `<Icon icon=""/>` 元件,在 icon 屬性填入 icon 的名稱,例如 [material-symbols:king-bed](https://icon-sets.iconify.design/material-symbols/king-bed/) : ```jsx // 安裝 // 官方文件 : https://iconify.design/docs/icon-components/vue/#installation npm install --save-dev @iconify/vue // plugins/iconify.js // 將 Icon 的功能加入到 Nuxt 插件註冊全域元件 import { Icon } from '@iconify/vue'; export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.component('Icon', Icon); }); // pages/index.vue // 使用 icon <Icon class="mb-2" icon="material-symbols:king-bed" /> ``` ## 回報流程 將答案上傳至 GitHub 並複製 GitHub repo 連結貼至底下回報就算完成了喔 ! 解答位置請參考下圖(需打開程式碼的部分觀看) ![](https://i.imgur.com/vftL5i0.png) <!-- 解答 : https://github.com/jasonlu0525/nuxt3-live-answer/tree/day22-swiper --> 回報區 --- | # | Discord | Github / 答案 | | --- | ----- | ----- | |1|眼睛|[Github](https://github.com/Thrizzacode/nuxt3-live-question/tree/day22-swiper)| | 2 | Steven |[Github](https://github.com/y7516552/nuxt3-live-question/tree/day22)| | 3 | dragon |[Github](https://github.com/peterlife0617/2024-nuxt-training-homework01/tree/feature/day22)| |4 | Lilith C | [Github](https://github.com/lilithchen/nuxt3-day22-swiper/tree/master) |5 | Johnson | [Github](https://github.com/tttom3669/2024_hex_nuxt_daily/tree/day22-swiper)| | 6 | Rocky |[Github](https://github.com/WuRocky/Nuxt-Day22-Swiper.git)| | 7 | LinaChen |[Github](https://github.com/Lina-SHU/nuxt3-live-question)| |8|hsin yu|[Github](https://github.com/dogwantfly/nuxt3-daily-task-live-question/tree/day22-swiper)| <!-- |---|---|[Github]()| -->