以下のような感じでSwiper.jsを使って、pagenationの中にnavigationを入れて前後のスライドに移動できるようにするようなデザインを作りたいときにデモやネット上にもあまり情報が無く、結構悩んだのですが一定出来たのでやり方をメモしておきます📝
※CSS等を使ったもっといいやり方があるかもしれないです。
前提事項
以下の環境での実装で検証したものになります。
"vue": "^2.6.11", "swiper": "^5.4.5", "vue-awesome-swiper": "^4.1.1",
実装memo
以下のような実装方針で今回は実装してみました。
- pagenationのnodeを取得
swiper
オブジェクトを引数に前/後のnavigationのインスタンスを生成- pagenationの一番最初に前、最後に後のnavigationを挿入
- navigationを押下時にpropsで渡した
swiper
オブジェクトに前/後のスライドに移動するメソッドを呼び出す。
以下がswiperをwrapしたコンポーネントです。
Swiperの生成と管理する要素の設置、そしてmouted
でpagenation内に前/後のボタンを配置しています。
※ pagination
のclickable
がtrueにしたときにpagenationクリック時の挙動がおかしくなります。。。おそらく要素のindexを使っており、pagenation内に他の要素が入るとおかしくなってそう?
<template> <div class="carousel"> <swiper ref="mySwiper" :options="swiperOptions"> <swiper-slide>Slide 1</swiper-slide> <swiper-slide>Slide 2</swiper-slide> <swiper-slide>Slide 3</swiper-slide> <swiper-slide>Slide 4</swiper-slide> <swiper-slide>Slide 5</swiper-slide> <div slot="pagination" class="swiper-pagination" /> </swiper> </div> </template> <script> import Vue from "vue"; import { Swiper, SwiperSlide } from "vue-awesome-swiper"; import CarouselPagenationNav from "./CarouselPagenationNav.vue"; import "swiper/css/swiper.min.css"; export default { name: "Carousel", components: { Swiper, SwiperSlide, }, data() { return { swiperOptions: { loop: true, centeredSlides: true, slidesPerView: 1.5, watchOverflow: true, spaceBetween: 16, pagination: { el: ".swiper-pagination", clickable: false // clickableはfalseにしないとうまく動かない }, }, }; }, computed: { swiper() { return this.$refs.mySwiper.$swiper; }, }, mounted() { const pagenation = this.$el.querySelector("div.swiper-pagination"); const ComponentClass = Vue.extend(CarouselPagenationNav); // NOTE: 動的コンポーネントで送出したemitを親コンポーネントで補足する方法がわからなかったので、 // 仕方無しにpropsで引き回して子コンポーネントでswiperのslide操作を行っている。 const prevBtn = new ComponentClass({ propsData: { swiper: this.swiper, type: "prev" } }); const nextBtn = new ComponentClass({ propsData: { swiper: this.swiper, type: "next" } }); prevBtn.$mount(); nextBtn.$mount(); // pagenationの最初に戻るのボタンを配置 pagenation.insertBefore(prevBtn.$el, pagenation.children[0]); // pagenationの最後に次へのボタンを配置 pagenation.appendChild(nextBtn.$el); }, methods: {}, }; </script> <style lang="scss"> .carousel { .swiper-container { color: white; .swiper-slide { background-color: burlywood; height: 250px; } } } </style>
以下が前後のボタンのコンポーネントです。
前か後かによってクリック時に実行する処理をslidePrev()
or slideNext()
で切り替えています。
あとついでにpagenationとnavigationは<swiper>...</swiper>
の外に配置し、スライドの外に表示されるようにしています。
<template> <button class="carousel-pagenation-nav" @click="handleOnClick"> {{ typeText }} </button> </template> <script> export default { name: "CarouselPagenationNav", props: { swiper: { type: Object, required: true, }, type: { type: String, required: true, }, }, data() { return {}; }, computed: { isPrev() { return this.type == "prev"; }, typeText() { return this.isPrev ? "<" : ">"; }, }, methods: { handleOnClick() { // propsで受け取ったswiperに対して戻る/進むのメソッドを実行 this.isPrev ? this.swiper.slidePrev() : this.swiper.slideNext(); }, }, }; </script> <style lang="scss"></style>
2021/01/17 追記
以下のような感じにすると特に動的にボタンを追加したりしなくても良さそうだったので追記
諸々修正していますがポイントは、以下です。
- navigationに使う要素のclass名は
swiper-button-prev
、swiper-button-next
以外の値にしてデフォルトのスタイルを当てないようにして、スタイルのカスタマイズができるように - pagenationとnavigationの要素はspanにして横並びで表示させられるように
<template> <div class="carousel"> <swiper ref="mySwiper" :options="swiperOptions"> <swiper-slide>Slide 1</swiper-slide> <swiper-slide>Slide 2</swiper-slide> <swiper-slide>Slide 3</swiper-slide> <swiper-slide>Slide 4</swiper-slide> <swiper-slide>Slide 5</swiper-slide> </swiper> <div class="swiper-controll"> <!-- NOTE: swiper-button-prev/nextにすると --> <!-- swiperのデフォルトのnavigationのスタイルがあたってしまうので別のclass名を設定 --> <span class="button-prev"><</span> <span slot="pagenation" class="swiper-pagination" /> <span class="button-next">></span> </div> </div> </template> <script> import { Swiper, SwiperSlide } from "vue-awesome-swiper"; import "swiper/css/swiper.min.css"; export default { name: "Carousel", components: { Swiper, SwiperSlide }, data() { return { swiperOptions: { slidesPerView: 1.5, slidesPerGroup: 1, slidesPerColumn: 1, slidesPerColumnFill: "column", centeredSlides: true, loop: true, watchOverflow: true, spaceBetween: 16, breakpoints: { 480: { slidesPerView: 3, slidesPerGroup: 3, slidesPerColumn: 2, slidesPerColumnFill: "row", loop: false, centeredSlides: false, }, }, navigation: { nextEl: ".button-next", prevEl: ".button-prev", }, pagination: { el: ".swiper-pagination", clickable: true // clickable: trueにしても動く }, }, }; }, computed: { swiper() { return this.$refs.mySwiper.$swiper; }, }, methods: {}, }; </script> <style lang="scss"> // scopedにしたいけど.swiper-pagination-bulletのstyleが当たらないのでscopedにしていない。 // https://github.com/surmon-china/vue-awesome-swiper/issues/22 .carousel { .swiper-container { color: white; width: 100%; .swiper-slide { background-color: burlywood; height: 250px; } } .swiper-controll { margin-top: 5px; text-align: center; vertical-align: middle; .button-next, .button-prev { display: inline-block; border-radius: 50%; color: #ffffff; background-color: #007aff; height: 24px; width: 24px; padding: 0; @media screen and (max-width: 480px) { display: none; } } .swiper-pagination { position: static; display: inline; .swiper-pagination-bullet { margin: 0 2px 0 2px; } } } } </style>