---
# System prepended metadata

title: "\U0001F3C5 Day 22"
tags: [2024 Nuxt 直播班]

---

# 🏅 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]()|
 -->