0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HubSpotのHubLでカルーセルを作って同一ページに複数配置したら干渉しちゃった話

Posted at

同一ページに複数のSwiperカルーセルを配置すると干渉する問題の解決方法

Cursorにカルーセルを作ってもらったら、最終的におかしなことに…
「ここが変だから直して」
「こっちもおかしいから直して」
「そこのコードはいじらないでっていったでしょ」
とやり取りをして直してもらったので、そのメモをCursorに書いてもらいました。

なんだったのか

  • グローバルセレクタによるCSS/JSの競合が原因
  • クラス名のスコープ化とユニークな識別子で解決
  • グローバル変数をdata属性に置き換えて複数インスタンスに対応

環境

  • Swiper.js 11.x
  • HubSpot CMS(カスタムモジュール)

問題の症状

1. ナビゲーション矢印の干渉

  • モジュールAの矢印をクリック → モジュールBも動く
  • ページネーションドットも同様に干渉

2. センターモードの崩壊

  • 単体では正常に動作
  • 同一ページに複数配置すると、後から読み込まれたモジュールがレイアウトを破壊

3. グローバル変数の上書き

  • autoplay設定などが複数インスタンス間で競合

原因分析

CSS セレクタの競合

/* ❌ 問題のあるコード */
.swiper-slide {
  width: 480px !important;
}

.swiper-slide-active .card {
  transform: scale(1.06);
}

問題点:

  • .swiper-slideがグローバルセレクタ
  • 全てのSwiperインスタンスに影響

JavaScript セレクタの競合

// ❌ 問題のあるコード
navigation: {
  nextEl: ".swiper-button-next",  // グローバルセレクタ
  prevEl: ".swiper-button-prev",
}

問題点:

  • querySelectorが最初にマッチした要素のみを返す
  • 複数カルーセルで同じ要素が操作対象になる

グローバル変数の競合

// ❌ 問題のあるコード
<script>
  var autoplayEnabled = {{ module.carousel.autoplay|lower }};
  var autoplayDelay = {{ module.carousel.autoplay_ms or 4000 }};
</script>

問題点:

  • 後から読み込まれたモジュールが変数を上書き
  • 全インスタンスが最後の設定値を参照

解決策

1. CSS セレクタのスコープ化

/* ✅ 修正後 */
.lpSwiper .swiper-slide {
  width: 480px !important;
}

.lpSwiper .swiper-slide-active .lp-card {
  transform: scale(1.06);
}

.hubdbSwiper .swiper-slide {
  width: 480px !important;
}

.hubdbSwiper .swiper-slide-active .hubdb-card {
  transform: scale(1.06);
}

ポイント:

  • モジュール固有のルートクラスでスコープ化
  • メディアクエリ内も全てスコープ化が必要

2. ユニークなナビゲーションセレクタ

HTML:

<!-- LP Carousel -->
<div class="swiper lpSwiper">...</div>
<div class="lpSwiper-controls">
  <div class="swiper-button-prev lp-button-prev"></div>
  <div class="lp-swiper-pagination"></div>
  <div class="swiper-button-next lp-button-next"></div>
</div>

<!-- HubDB Carousel -->
<div class="swiper hubdbSwiper">...</div>
<div class="hubdbSwiper-controls">
  <div class="swiper-button-prev hubdb-button-prev"></div>
  <div class="hubdb-swiper-pagination"></div>
  <div class="swiper-button-next hubdb-button-next"></div>
</div>

JavaScript:

// ✅ 修正後
allCarousels.forEach(function(rootEl) {
  // コントロール要素を兄弟要素から探す
  var controlsEl = rootEl.nextElementSibling;
  while (controlsEl && !controlsEl.classList.contains('lpSwiper-controls')) {
    controlsEl = controlsEl.nextElementSibling;
  }

  var swiperConfig = {
    // ...
    pagination: {
      el: controlsEl ? controlsEl.querySelector('.lp-swiper-pagination') : null,
    },
    navigation: {
      nextEl: controlsEl ? controlsEl.querySelector('.lp-button-next') : null,
      prevEl: controlsEl ? controlsEl.querySelector('.lp-button-prev') : null,
    },
  };

  new Swiper(rootEl, swiperConfig);
});

ポイント:

  • 各カルーセルインスタンスごとに個別のコントロール要素を探索
  • querySelectorのスコープを制限

3. グローバル変数をdata属性に置き換え

HTML:

<!-- ✅ 修正後 -->
<div class="swiper lpSwiper" 
     data-autoplay="{{ module.carousel.autoplay|lower }}" 
     data-autoplay-delay="{{ module.carousel.autoplay_ms or 4000 }}">
  <!-- ... -->
</div>

JavaScript:

// ✅ 修正後
allCarousels.forEach(function(rootEl) {
  // data属性から設定を読み取る
  var autoplayEnabled = rootEl.getAttribute('data-autoplay') === 'true';
  var autoplayDelay = parseInt(rootEl.getAttribute('data-autoplay-delay'), 10) || 4000;

  var swiperConfig = {
    // ...
  };

  if (autoplayEnabled) {
    swiperConfig.autoplay = {
      delay: autoplayDelay,
      disableOnInteraction: false,
      pauseOnMouseEnter: true
    };
  }

  new Swiper(rootEl, swiperConfig);
});

ポイント:

  • インスタンスごとに独立した設定を保持
  • グローバルスコープを汚さない

4. センターモードの確立

/* overflow設定の調整 */
.lpSwiper {
  overflow: hidden;  /* visibleからhiddenに変更 */
  padding-bottom: 40px;  /* 拡大時の影が切れないように */
  margin-bottom: -40px;  /* 余白を相殺 */
}

.lpSwiper .swiper-wrapper {
  display: flex;
  align-items: center;
}
// 初期化時に明示的にスライド位置を設定
on: {
  init: function (sw) {
    sw.update();
    if (slideCount > 1) {
      sw.slideToLoop(0, 0, false);  // 最初のスライドに移動
    }
    // ...
  }
}

結果

✅ 複数のSwiperインスタンスが完全に独立して動作
✅ ナビゲーションの干渉なし
✅ センターモードが正常に機能
✅ 各インスタンスが個別の設定を保持

まとめ

複数のSwiperカルーセルを同一ページに配置する際は:

  1. CSS/JSセレクタを必ずスコープ化(モジュール固有のクラスで囲む)
  2. ナビゲーション要素にユニークな識別子を付与
  3. グローバル変数を避け、data属性やクロージャで管理
  4. DOM探索はインスタンスごとにスコープを制限

これらを徹底することで、カルーセルの干渉問題を完全に解決できます。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?