[Vue3 + Swiper8] 独自デザインのナビゲーションとページネーションを実装する


# はじめに

高機能カルーセルライブラリといえば自分は Swiper をおすすめします。
Vue、React、Angularなどに対応し、カスタマイズ機能が豊富。

https://swiperjs.com/

ただしバージョン毎にクセがあって、アップデートの度に導入トラブルで苦しめられていたのですが（自分の技量が足りない）、最新版の 8.0.5 ではスムーズに導入できたので備忘録として残します。

https://swiperjs.com/vue

# インストール

`swiper` をインストールするだけです。

```bash
$ yarn add swiper
```

もし `css-loader`、`vue-style-loader` がなければインストールします。

```bash
$ yarn add --dev css-loader vue-style-loader
```

# Webpack設定

この記事では Vue での開発環境が整っている前提ですので詳細は省きます。
`webpack.config.js` の `module.rules` に CSS設定を追加します。

```js:webpack.config.js
module.exports = {
  // 〜略〜
  module: {
    rules: [
      // Vue設定
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        use: [{
          loader: 'vue-loader',
        }],
      },
      // css設定
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader'],
      }
    ],
  },
  // 〜略〜
}
```

## `style-loader` も入れたらエラーになった

上の結論になる前に `style-loader` も入れていたのですが、実行時にエラーになりました。
この辺の組み合わせはいまだによくわかってません。

```js
// 実行時にエラー
use: ['vue-style-loader', 'style-loader', 'css-loader'],
// 問題なし
use: ['vue-style-loader', 'css-loader'],
```



# コンポーネント実装

Vue3 の `<script setup>` 構文を使って書いています。

```vue:
<script setup lang="ts">
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';

const images = [
  'https://i.picsum.photos/id/925/600/400.jpg?hmac=VxvBfviRrdHXsJ6_nbZ00wdqs0AEutxmI1ughFU8KQU',
  'https://i.picsum.photos/id/428/600/400.jpg?hmac=grP0p8M-3MohHSsDfu7ZXo9VGbL-OUSCDScCUZu8ps4',
  'https://i.picsum.photos/id/340/600/400.jpg?hmac=fUPfZKTRNEKns8xR8vPzDByTCfY9cEbh5lFLG8rP8H8',
  'https://i.picsum.photos/id/702/600/400.jpg?hmac=KxRAc7VlNyemdt4RTHqjBvy9heWN7FV0qHG_U2OyOjo',
];

// Swiperのインスタンスが返ってくる
const onSwiper = (swiper) => {
  console.log('swiper', swiper);
};

// スライド位置が変更された時に呼ばれる
const onSlideChange = () => {
  console.log('slide change');
};
</script>

<template>
  <div class="story-visuals">
    <swiper @swiper="onSwiper" @slideChange="onSlideChange">
      <swiperSlide v-for="(image, index) in images" :key="`slide-${index}`">
        <img :src="image" />
      </swiperSlide>
    </swiper>
  </div>
</template>
```

今回は拍子抜けするくらいあっさりと実装できました。

# ナビゲーション、ページネーションを実装する

ナビゲーション、ページネーション機能の追加も簡単です。

```vue:
<script setup lang="ts">
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Navigation, Pagination, Virtual } from 'swiper';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';

〜略〜
</script>

<template>
  <div class="story-visuals">
    <swiper
      :modules="[Navigation, Pagination, Virtual]"
      navigation
      :pagination="{ clickable: true }"
      virtual
      @swiper="onSwiper"
      @slideChange="onSlideChange"
    >
      <swiperSlide v-for="(image, index) in images" :virtualIndex="index" :key="`slide-${index}`">
        <img :src="image" />
      </swiperSlide>
    </swiper>
  </div>
</template>

```

## 解説

```vue
import { Navigation, Pagination, Virtual } from 'swiper';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
```

ナビゲーション、ページネーションのモジュール、それぞれの CSS を読み込んでいます。
`Virtual` を付けると描画が Vue.js を処理するように変更されるようです。

>Virtual Slides rendering here is fully handled by Vue.js and not required anything except setting :virtual="true" property and setting virtualIndex on slides:

```vue
    <swiper
      :modules="[Navigation, Pagination, Virtual]"
      navigation
      :pagination="{ clickable: true }"
      virtual
      @swiper="onSwiper"
      @slideChange="onSlideChange"
    >
      <swiperSlide v-for="(image, index) in images" :virtualIndex="index" :key="`slide-${index}`">
        <img :src="image" />
      </swiperSlide>
    </swiper>
```

追加したモジュールを `modules` 属性で登録しています。
有効化のために `navigation`、`pagination`、`virtual` 属性を付与しています。
`pagination` 属性ではバレット（ページネーションの●）のクリック可を指定してます。


# ナビゲーション、ページネーションのデザインをカスタマイズする

標準のCSSをコメントアウトして、独自CSSを追加しています。
設定が追加されて `<swiper>` が読みづらくなるので、設定を変数に書き出しました。
**`Virtual` が有効だと上手く動作しないので外しました。**

```vue
<script setup lang="ts">
import { Navigation, Pagination } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';

// 標準CSSは除外
// import 'swiper/css/navigation';
// import 'swiper/css/pagination';

// 〜略〜

const swiperModules = [Navigation, Pagination];

// ナビゲーションで使うエレメントのクラスを指定
const swiperNavigation = {
  prevEl: '.story-navi--prev',
  nextEl: '.story-navi--next',
};

// ページネーションで使うエレメントのクラスを指定
// 画像をサムネイルとして並べたいのでバレットの内容を指定
const swiperPagination = {
  el: '.story-thumbs',
  clickable: true,
  renderBullet: (index, className) =>
    `<span class="story-thumbs__item ${className} 1"><img src="${images[index]}" style="width:100%"></span>`,
};
</script>

<!-- スタイルシート -->
<style>
.story-visuals {
  width: 600px;
}
.story-thumbs {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
<!-- ページネーションのアクティブ以外は透明度 0.4 -->
.story-thumbs__item {
  opacity: 0.4;
}
.story-thumbs__item.swiper-pagination-bullet-active {
  opacity: 1;
}
</style>

<template>
  <div class="story-visuals">
    <swiper
      :modules="swiperModules"
      :pagination="swiperPagination"
      :navigation="swiperNavigation"
      :loop="true"
      @swiper="onSwiper"
      @slideChange="onSlideChange"
    >
      <swiperSlide v-for="(image, index) in images" :key="`slide-${index}`">
        <img :src="image" />
      </swiperSlide>
    </swiper>

    <!-- サムネイル一覧 -->
    <div class="story-thumbs" />

    <!-- 左右移動 -->
    <span class="story-navi story-navi--prev">戻る</span>
    <span class="story-navi story-navi--next">進む</span>
  </div>
</template>

```

## 解説

```vue
const swiperPagination = {
  el: '.story-thumbs',
  clickable: true,
  renderBullet: (index, className) =>
    `<span class="story-thumbs__item ${className} 1"><img src="${images[index]}" style="width:100%"></span>`,
};
```

`renderBullet` はページネーションで使用するHTMLを記載します。
画像をサムネイルとして並べたかったので `index` で何番目かを取り出して画像取得に利用しています。

`className` にはアクティブなバレットのクラス `swiper-pagination-bullet-active` が入ります。

```vue
    <swiper
      :modules="swiperModules"
      :pagination="swiperPagination"
      :navigation="swiperNavigation"
      :loop="true"
      @swiper="onSwiper"
      @slideChange="onSlideChange"
    >
```

変数に書き出した設定を指定しています。
`loop` はカルーセルの無限ループです。

`@swiper`、`@slideChange` はデバッグ用なので不要なら消してOKです。


# おわり

Swiper は最初のとっかかりがわかりにくいのですが、そこを乗り越えれば大変便利なライブラリです。
この記事が一助になれば嬉しいです。