はじめに

ウェブサイトでギャラリーや商品紹介などを行う際、複数の画像をスライドショー形式で表示するモーダルは非常に有効です。今回は、Swiperライブラリを使用して複数のスライド可能なモーダルを実装し、各モーダル内の特定のスライドを直接開く方法を解説します。

HTML構造

まず、モーダルを開くためのトリガー要素とモーダル自体のHTML構造を見てみましょう。

トリガー要素


  <div class="masonry-container masonry-viewModalTmb">
    <ul id="gallery-1" class="masonry-wrapper">
      <li class="masonry-slide">
        <a class="masonry-ifield js-opnr4KVA" data-opento="sdl-peji4KVA">
          <picture class="masonry-thumbnail">
            <img src="画像のURL" alt="">
          </picture>
        </a>
      </li>
      <!-- 他のスライド -->
    </ul>
  </div>
  

モーダル本体


<dialog id="sdl-peji4KVA" class="swiper-container swiper-viewModal">
  <ul class="swiper-wrapper">
    <li class="swiper-slide">
      <a class="swiper-ifield">
        <picture class="swiper-thumbnail">
          <img src="画像のURL" alt="">
        </picture>
        <div class="swiper-caption">
          <h4 class="swiper-title">Image:01</h4>
        </div>
      </a>
    </li>
    <!-- 他のスライド -->
  </ul>
  <ol class="swiper-pagination"></ol>
  <nav class="swiper-navigation">
    <a class="swiper-button-prev" aria-label="Previous slide"></a>
    <a class="swiper-button-next" aria-label="Next slide"></a>
  </nav>
  <a class="swiper-closer js-clsr4KVA" aria-label="閉じる">×</a>
</dialog>

ポイント:

  • トリガー要素にはdata-opento属性を使用して、開くべきモーダルのIDを指定します。
  • モーダル本体は<dialog>タグを使用し、Swiperの構造に従っています。
  • 各モーダルに一意のIDを付与します(例:id="sdl-peji4KVA")。

JavaScript実装

次に、モーダルとSwiperを制御するJavaScriptのコードを見ていきます。


(function () {
  const swipOpnrs = Array.from(document.querySelectorAll(".js-opnr4KVA"));
  const swipClsrs = Array.from(document.querySelectorAll(".js-clsr4KVA"));
  const { body } = document;
  
  let timer4KVA = 0;
  let promiseSwpModal4KVA = function () {
    if (timer4KVA) clearTimeout(timer4KVA);
    timer4KVA = setTimeout(() => {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          // トリガー要素にdata-slideto属性を付与
          const modalGroups = {};
          swipOpnrs.forEach((swipOpnr) => {
            const modalId = swipOpnr.getAttribute('data-opento');
            if (!modalGroups[modalId]) {
              modalGroups[modalId] = [];
            }
            modalGroups[modalId].push(swipOpnr);
          });
  
          Object.values(modalGroups).forEach((group) => {
            group.forEach((swipOpnr, index) => {
              swipOpnr.setAttribute('data-slideto', index + 1);
            });
          });
  
          resolve();
        }, 0);
      })
        .then(function () {
          return new Promise(function (resolve, reject) {
            setTimeout(function () {
              // Swiperの初期化
              let swipCtnrs = Array.from(document.querySelectorAll("dialog.swiper-container.swiper-viewModal"));
              const swiperInstances = {};
  
              swipCtnrs.forEach((tgtCtnr) => {
                const modalId = tgtCtnr.id;
                swiperInstances[modalId] = new Swiper(tgtCtnr, {
                  // Swiper設定
                });
  
                // キャンセルイベントの処理
                tgtCtnr.addEventListener('cancel', () => {
                  tgtCtnr.classList.remove('is-opened');
                  body.classList.remove('is-modaled');
                });
              });
  
              // モーダルを開く処理
              swipOpnrs.forEach((swipOpnr) => {
                swipOpnr.addEventListener('click', function (e) {
                  e.preventDefault();
                  const modalId = this.getAttribute('data-opento');
                  const tgtCtnr = document.getElementById(modalId);
                  const swiper = swiperInstances[modalId];
  
                  if (tgtCtnr && swiper) {
                    const dataSlideTo = this.getAttribute('data-slideto');
                    const intSlideTo = parseInt(dataSlideTo) || 1;
                    swiper.slideTo(intSlideTo);
                    tgtCtnr.showModal();
                    tgtCtnr.classList.add('is-opened');
                    body.classList.add('is-modaled');
                  }
                });
              });
  
              // モーダルを閉じる処理
              swipClsrs.forEach((swipClsr) => {
                swipClsr.addEventListener('click', function (e) {
                  e.preventDefault();
                  const tgtCtnr = this.closest('dialog');
                  if (tgtCtnr) {
                    tgtCtnr.close();
                    tgtCtnr.classList.remove('is-opened');
                    body.classList.remove('is-modaled');
                  }
                });
              });
  
              resolve();
            }, 0);
          });
        })
        .catch(function (error) {
          console.log(error);
        });
    }, 300);
  };
  
  // イベントリスナーの設定
  ['load', 'resize'].forEach((events) => {
    window.addEventListener(events, promiseSwpModal4KVA);
  });
  
  imagesLoaded(swipOpnrs, function (e) {
    promiseSwpModal4KVA();
  });
  })();

実装のポイント

  1. モーダルグループの管理:
  • 各モーダルに対応するトリガー要素をグループ化し、data-slideto属性を付与します。これにより、各モーダル内の特定のスライドを直接開くことができます。
  1. Swiperインスタンスの管理:
  • 各モーダルのIDをキーとして、Swiperインスタンスを管理するオブジェクトを作成します。これにより、複数のモーダルを個別に制御できます。
  1. モーダルを開く処理:
  • トリガー要素のクリック時に、対応するモーダルとSwiperインスタンスを特定し、指定されたスライドを表示します。
  1. モーダルを閉じる処理:
  • 閉じるボタンのクリックとキャンセルイベント(Escキー)の両方に対応します。
  1. 遅延処理とPromise:
  • setTimeoutPromiseを使用して、DOM操作や初期化を適切なタイミングで実行します。
  1. リサイズ対応:
  • ウィンドウのリサイズイベントに対応し、レイアウトの変更に対応します。
  1. 画像の読み込み完了後の処理:
  • imagesLoadedライブラリを使用して、画像の読み込みが完了した後に初期化処理を実行します。

まとめ

この実装により、複数のSwiperモーダルを持つページで、各モーダル内の特定のスライドを直接開くことができます。<dialog>タグとSwiperライブラリを組み合わせることで、アクセシビリティと使いやすさの両面で優れたUIを提供できます。

実際の使用時には、パフォーマンスの最適化(画像の遅延読み込みなど)やエラーハンドリングの強化を検討するとよいでしょう。また、CSSを適切に設定し、モーダルの見た目や動きを調整することで、よりユーザーフレンドリーなインターフェースを作成できます。