写真ギャラリーとスライド式モーダルウィンドウを連携させる方法
写真ギャラリーとスライド式モーダルウィンドウを連携させる方法

写真ギャラリーとスライド式モーダルウィンドウを連携させる方法

Webサイトで綺麗な写真を並べるギャラリーコンテンツを作成するケースは多いです。 ただ画像を並べるだけでなく、写真をクリックしたらモーダルウィンドウで拡大表示され、さらにそこでスライドショーとして他の画像も確認できる、といったリッチなUIが求められることも少なくありません。

本記事では、「ギャラリー自体もスライダー」であり、かつ「画像クリックで開くモーダルもスライダー」という、Swiperを2つ連動させる実装の備忘録です。

特にギャラリー側をループ(loop: true)設定にすると、Swiperがスライドを複製(duplicate)するため、クリックした画像とモーダルで表示する画像のインデックスがずれやすい、という問題が起こりがちです。

今回の実装は、その「インデックスのずれ」をdata属性を使って解決する方法です。Swiperのバージョンは11を使用しています。同じ実装で悩む方の参考になれば幸いです。

実装の概要とポイント

まず、完成形(デモ)の前に、実装の主要なロジックを解説します。

HTML側:

ギャラリーの各画像(スライド)に、data-index="0", data-index="1"のように、ユニークな番号(インデックス)をdata属性として付与します。

JavaScript側:

まず、ページの読み込み時に、data-indexを元に全画像のソース(src)を正しい順番で配列(imageSources)に格納しておきます。(※これがループ対策の肝です)

ギャラリーの画像がクリックされたら、その画像のdata-indexの値を取得します。

モーダルを開くと同時に、手順1で作成したimageSources配列を元に、モーダル用のSwiperスライドを動的に生成します。

モーダル用のSwiperを初期化(new Swiper(…))する際、initialSlideオプションに手順2で取得したdata-indexの値を設定します。

デモ(CodePen)

上記ロジックで実装したデモがこちらです。 ギャラリー自体が横スライド(ループあり)で、画像をクリックするとモーダルが起動し、クリックした画像からスライド(ループあり)が開始されます。

コードの詳細解説

CodePenのソースと合わせて、主要なロジックを解説します。

1. 画像配列の作成(ループ対策)

Swiperのloop: trueを有効にすると、スライドが複製されるため、document.querySelectorAllなどで画像を取得するだけでは、順番が狂ったり、重複した画像を含んだ配列になってしまいます。

そこで、loop: trueの影響を受けないよう、data-index属性を元にして、オリジナルの画像ソースだけを順番通りに配列へ格納します。

// 最初にすべての画像ソースを順番に取得して配列に保存
const imageSources = [];
const galleryItems = document.querySelectorAll('.gallery_item');

// data-indexの最大値を探して画像の総数を決定
const maxIndex = Array.from(galleryItems).reduce((max, item) => {
    const index = parseInt(item.dataset.index, 10);
    return index > max ? index : max;
}, -1);

// data-indexの0から最大値までループ
for (let i = 0; i <= maxIndex; i++) {
    // querySelectorは重複する要素があっても最初の一つだけを返す
    // これにより、Swiperが複製したスライド(duplicate)を無視できる
    const imageElement = document.querySelector(`.gallery_item[data-index="${i}"] img`);
    if (imageElement) {
        imageSources.push(imageElement.src);
    }
}

ポイントは、document.querySelector()を使っている点です。

loop: trueの場合、data-index="0"の要素はDOM上に複数存在しますが、querySelectorはHTMLの上から最初に合致した要素を1つだけ返すため、オリジナルの要素を確実に取得できます。

2. 画像クリック時のモーダル表示

次に、クリックイベントの処理です。

const modalButtons = document.querySelectorAll('.modal_button');
const swiperWrapper = document.querySelector('.modal .swiper-wrapper');
let swiper; // モーダル用Swiperのインスタンスを保持する変数

modalButtons.forEach((button) => {
  button.addEventListener('click', (e) => {
    // 1. クリックされた画像のdata-indexを取得
    const slideIndex = parseInt(e.currentTarget.dataset.index, 10);

    // (スライドの中身を一旦クリアする処理...)
    swiperWrapper.innerHTML = '';

    // 2. 事前に作成した画像配列(imageSources)からスライドを動的に生成
    imageSources.forEach(src => {
      const slide = document.createElement('div');
      slide.classList.add('swiper-slide');
      // (img要素を作成して追加する処理...)
      swiperWrapper.appendChild(slide);
    });

    // 3. Swiperを初期化。initialSlideに取得したindexを指定
    swiper = new Swiper('.modal .swiper', {
      loop: true,
      initialSlide: slideIndex, // 押された画像からスタート
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
    });

    modal.classList.add('is-open');
  });
});

initialSlide: slideIndexとすることで、モーダルが開いた瞬間に、クリックされた画像が中央に表示されます。

応用:モーダルでの表示順を変更する

今回のデモでは、モーダルのスライド順はHTMLのdata-indexの昇順(0, 1, 2...)です。

もしこの順番を変更したい場合は、JavaScript側ではなく、HTML側のdata-indexの数値を変更するだけで対応できます。

例えば、data-index="0", data-index="2", data-index="1"の順に並べれば、モーダルもその順番でスライドします。 ただし、ギャラリーの見た目のレイアウト(CSS)とdata-indexの順序が大きく異なると、ユーザーに違和感を与える可能性があるため、設計に合わせて調整してください。

補足:ギャラリーがループしない(loop: false)場合のシンプルな実装

もし、**ギャラリー側がループしない(loop: false)**のであれば、わざわざdata-indexを使って事前に配列を作る必要はありません。 クリックされたインデックスをinitialSlideに渡すだけで、Swiperが自動で処理してくれます。

その場合のJavaScriptは以下のようになり、よりシンプルになります。 (※このコードはデモの「ギャラリー側Swiper」をloop: falseに設定した場合にのみ正しく動作します)

const modalButtons = document.querySelectorAll('.modal_button');
const modal = document.querySelector('.modal');
const closeButton = modal.querySelector('button');
const swiperWrapper = document.querySelector('.modal .swiper-wrapper');

let swiper;

modalButtons.forEach((button, index) => {
  button.addEventListener('click', () => {
    // すでにスライドがあればクリアする
    swiperWrapper.innerHTML = '';

    // すべての画像要素を直接取得
    const allImages = document.querySelectorAll('.gallery_item img');
    allImages.forEach(img => {
      const slide = document.createElement('div');
      slide.classList.add('swiper-slide');
      const image = document.createElement('img');
      image.src = img.src;
      slide.appendChild(image);
      swiperWrapper.appendChild(slide);
    });

    // Swiperを初期化(indexをそのままinitialSlideに使う)
    swiper = new Swiper('.swiper', {
      loop: true,
      initialSlide: index, // NodeListのindexをそのまま利用
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
    });

    modal.classList.add('is-open');
  });
});

// ... closeModalなどの処理 ...

// ギャラリー側は loop: false にする
const gallerySwiper = new Swiper('.swiper_gallery_columns', {
  slidesPerView: 'auto',
  loop: false, // ループしない
  spaceBetween: 20,
  navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev',
  },
});

まとめ

本記事では、ギャラリー(ループあり)とモーダル(ループあり)をSwiper v11で連携させる方法を紹介しました。

loop: trueによるスライドの複製問題を、data-index属性とquerySelectorの特性を使って回避する。

事前に正しい画像ソースの配列を用意し、モーダル表示時に動的にスライドを生成する。

initialSlideオプションにクリックされたdata-indexを渡す。

デモのソースは自由にコピーしてアレンジしていただいて構いません。 本記事で紹介したdata-indexを使う方法は、ギャラリーがループしない場合でも問題なく動作しますが、補足で紹介したシンプルな方法(loop: false用)を、ループありのギャラリーで使うとバグが発生するため、注意してください。

関連記事