見出し画像

3つの swiper を完全に同期・連動させる方法 (※controller で上手く動かない人向け)【Swiper 10系】

ここでいう「完全に同期」とは
「スライダーA」「スライダーB」「スライダーC」があるとき、

  • AをスライドさせればB・Cもスライドし、

  • BをスライドさせればC・Aもスライドし、

  • CをスライドさせればA・Bもスライドする

という状態のことを指します。

  • AをスライドさせればB・Cもスライドする(ただし、CをスライドしてもA・Bは動かない)

という一方向の連動は色々なウェブサイトで紹介されているのですが、双方向の連動についての情報が見当たらないため、自分で模索しました。


よくない例1

const swiper_A = new Swiper('.slider-a', {
    controller: {
        control: swiper_B,
    },
});

const swiper_B = new Swiper('.slider-b', {
    controller: {
        control: swiper_C,
    },
});

const swiper_C = new Swiper('.slider-c', {
    controller: {
        control: swiper_A,
    },
});

Swiper を起動(new Swiper)する時に controller プロパティを使って「A→B」「B→C」「C→A」と制御させるアイデア。
この方法を紹介しているウェブサイトもありますが、少なくとも2024年2月に筆者が swiper 9系/10系 で確認した限り、正常に動作しません。
動かない詳細な理由までは分かりません。Chrome DevTools のコンソール画面を見ても特にエラーは表示されていません。


よくない例2

const swiper_A = new Swiper('.slider-a', {
   // 略
});

const swiper_B = new Swiper('.slider-b', {
   // 略
});

const swiper_C = new Swiper('.slider-c', {
   // 略
});

A.controller.control = B
B.controller.control = C
C.controller.control = A

controller メソッドを使って「A→B」「B→C」「C→A」と制御させるアイデアも、うまく動作しません。
無限ループが発生し、Chrome DevTools にはスタックオーバーフローのエラーが吐かれます。


よい例 1(3つとも slideChangeTransitionEnd で制御させる)

const swiper_A = new Swiper('.slider-a', {
   loop: true,
   // 他の設定は略
});

const swiper_B = new Swiper('.slider-b', {
   loop: true,
   // 他の設定は略
});

const swiper_C = new Swiper('.slider-c', {
   loop: true,
   // 他の設定は略
});
    

let slideNum;

// A→B・C
swiper_A.on('slideChangeTransitionEnd', (slider) => {
    slideNum = slider.realIndex;
    swiper_B.slideToLoop(slideNum, undefined, false);
    swiper_C.slideToLoop(slideNum, undefined, false);
});

// B→A・C
swiper_B.on('slideChangeTransitionEnd', (slider) => {
    slideNum = slider.realIndex;
    swiper_A.slideToLoop(slideNum, undefined, false);
    swiper_C.slideToLoop(slideNum, undefined, false);
});

// C→A・B
swiper_C.on('slideChangeTransitionEnd', (slider) => {
    slideNum = slider.realIndex;
    swiper_A.slideToLoop(slideNum, undefined, false);
    swiper_B.slideToLoop(slideNum, undefined, false);
});

slideChangeTransitionEnd イベントを利用して、連動させるとよいです。
(※ slideChangeTransitionEnd … スワイプしてスライドが切り替わった直後に発火するイベント。→ Swiper API ドキュメント

Aのスライドが切り替わった直後、B・Cのスライドを同じ番号のものへスライドさせるイベント (slideToLoop) を発火しています。

この制御を3パターン用意することで「どのスライダーを動かしても残り2つが動く完全同期」が可能となります。

  • A → B・C

  • B → A・C

  • C → A・B

重要ポイントは「slideToLoop」の第三引数「runCallbacks」に false を指定すること
(※ runCallbacks … スライドした直後に「スライド関連のイベントを発火するか否か」を指定できる引数。→ Swiper API ドキュメント

false に設定しないと、「AがBを制御する。BがAを制御する。AがBを...」という無限ループが発生し、スタックオーバーフローになってしまいます。
false にすることで「A → B・C」の一方向だけで処理が終わり、「B → A(C → A)」というおかわりが発生しません。

※ なお、今回の例では、3つのスライダーとも「loop: true」に設定しているため「slideToLoop」メソッドで動かしていますが、「loop: false」ならもちろん「slideTo」を使うべきです。


よい例 2(slideChangeTransitionEnd イベントと thumbs プロパティを併用する)

「よい例 1」だけで問題ないように思えるかもしれませんが、実務上はそうとも限りません。

以下、少々脱線した説明になりますが、まず「よい例 1」では済まない具体例について紹介し、そのあと回避策としての「よい例 2」のコードを紹介します。

例えば、下記のようなウェブページを作りたいとします。(ECサイトでありがちなデザインです)

  • スライダーA … 本画面のメインスライダー

  • スライダーB … モーダルの中にあるスライダー(大)

  • スライダーC … モーダルの中にあるスライダー(サムネイル)

C のように「(1枚ではなく)複数の小さなサムネイルを並べる方式」の場合、「画像の総枚数 ÷ 2」が「一度に表示するサムネイルの数(=slidesPerView)」を下回ると、ループ表示がうまく動きません。
上の例では、一度に 6 枚を表示する設定にしている(slidesPerView: 6)ので、画像の総枚数が 12 枚以上でなければ上手くいきません。
(これは今回解説する「3つの swiper の連動」とは特に関係がなく、単体の swiper でも生じる問題です。なお、swiper 9系以上で発生するバグだそうです。)

しかし、バックエンドの仕様しだいでは、画像の総枚数を 12  枚以上にできない場合もあります。(また、12枚未満だからといって JavaScript で DOM を複製して水増しすれば解決するかというと….今度は、3つのスライダーの同期に問題が生じます。)

ごちゃごちゃ説明しましたが、このようにループを諦めるしかない場合があります。
そして、「スライダーA・B」はループするが「スライダーC」はループしないという設定の場合、先ほど紹介した「よい例 1」のコードはうまく動きません。
「slideToLoop」と「slideTo」を併用すればうまくいくように思えるかもしれませんが、筆者が試行錯誤した限りうまくいきません。

このような場面では「slideChangeTransitionEnd イベントと thumbs プロパティを併用する」というアイデアが役に立ちます。

const swiper_A = new Swiper('.slider-a', {
   loop: true,
});

// Cを先に定義する
const swiper_C = new Swiper('.slider-c', {
   loop: false,
});

const swiper_B = new Swiper('.slider-b', {
   loop: true,
   // サムネイル設定。BとCを連動させる。
   thumbs: {
        swiper: swiper_C, // 先に定義しておかないとこの行がエラーとなる
   },
});
    

let slideNum;

// A→Bの制御
swiper_A.on('slideChangeTransitionEnd', (slider) => {
    slideNum = slider.realIndex;
    swiper_B.slideToLoop(slideNum, undefined, false);
});

// B→Aの制御
swiper_B.on('slideChangeTransitionEnd', (slider) => {
    slideNum = slider.realIndex;
    swiper_A.slideToLoop(slideNum, undefined, false);
});

B と C は、swiper を起動させる(new Swiper)とき、「thumbs」プロパティを設定することで連動させています。
「BのサムネイルはCである」と設定すると、あとは勝手に連動してくれます。Bを動かすとCも動き、Cを動かすとBも動きます。

(※ このとき、JavaScript の仕様上、Cを先に定義する必要があることに気をつけてください。まだ定義されていないものを thumbs に指定するとエラーになります。)

そして、A と B は「よい例 1」で紹介したとおり、slideChangeTransitionEnd イベントで連動させています。

この設定によって、A・B・C が完全に同期するようになります。



この note が役に立ったら、SNSでシェアして頂けますと有り難いです。

他にも、web制作のノウハウをまとめた note を投稿しています。マガジン「Yanada's Note」にまとめていますので、ぜひご覧ください。



この記事が気に入ったらサポートをしてみませんか?