HubSpotモジュール:ロゴカルーセル

これは、クライアント企業やパートナー企業のロゴを横スクロールアニメーションで表示するモジュールです。

Demo Video

Detail

これは、クライアント企業やパートナー企業などのロゴを横スクロールアニメーションで表示するモジュールです。

利用目的

サービスの 信頼性や権威性(ソーシャルプルーフ)を視覚的に訴求 するために、クライアント事例やパートナー企業の一覧を表示します。

実現できること

  • 編集者は、ロゴ画像を 自由に追加・削除・並べ替え 可能。
  • 追加されたロゴは横一列に並び、無限かつ自動でスクロールします。
  • 各ロゴには、任意で参照ページへのリンク を設定可能。

実装ポイント & 注意事項

  • フィールド設定:
    モジュールのフィールド設定で グループフィールドを作成し、リピーター(repeater)を有効化
    グループ内には以下を配置:
    • Image フィールド(ロゴ画像用)
    • URL フィールド(リンク用)
  • 無限スクロールの実装方法:
    1. CSS アニメーションのみ
      • HubL ループを2回走らせてロゴリストを複製。
      • @keyframestransform: translateX() を使って全体をアニメーションさせ、ライブラリ不要のスムーズな無限スクロールを実現。
    2. JavaScript ライブラリ使用
      • Swiper.js などの軽量スライダーライブラリを組み込み、モジュール専用のJSファイルで初期化。
      • 一時停止・再生やドラッグ操作などの高度な機能が必要な場合に適している。
  • 画像最適化:
    編集者向けのヘルプテキストでは、以下を推奨することが重要:
    • 透過背景の PNG 画像を使用
    • すべての画像の高さを揃える
      → スライダー全体のデザイン品質を維持できる。

フィールド設定例

  • ① Group
    • HubL 変数名: logos
    • フィールドタイプ: Group
    • (注意)グループ設定を行う場合は、まずフィールドを作成し、その後にグループを組成してください。

  • ①-1: Image
    • HubL 変数名: image
    • フィールドタイプ: image

Source Code

HTML
{% set module_id = name ~ '_' ~ widget.id %}
<div class="logo-slider-wrapper" id="{{ module_id }}">
  <div class="logo-slider-container">
    <div class="logo-slider-track">
      {% for logo in module.logos %}
        {% if logo.image.src %}
          <div class="logo-slider-item">
            <img src="{{ logo.image.src }}" 
                 alt="{{ logo.image.alt or 'Company Logo' }}" 
                 loading="lazy">
          </div>
        {% endif %}
      {% endfor %}
      {% for logo in module.logos %}
        {% if logo.image.src %}
          <div class="logo-slider-item">
            <img src="{{ logo.image.src }}" 
                 alt="{{ logo.image.alt or 'Company Logo' }}" 
                 loading="lazy">
          </div>
        {% endif %}
      {% endfor %}
    </div>
  </div>
</div>

CSS
.logo-slider-wrapper {
  width: 100%;
  overflow: hidden;
  background: #ffffff;
  padding: 40px 0;
  position: relative;
}
.logo-slider-container {
  position: relative;
  width: 100%;
  max-width: 100%;
}
.logo-slider-track {
  display: flex;
  align-items: center;
  white-space: nowrap;
  animation: logoSlide 25s linear infinite;
  will-change: transform;
}
.logo-slider-item {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 200px;
  height: 100px;
  margin-right: 60px;
  padding: 15px;
}
.logo-slider-item img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  filter: grayscale(100%);
  opacity: 0.7;
  transition: all 0.3s ease;
}
.logo-slider-item:hover img {
  filter: grayscale(0%);
  opacity: 1;
  transform: scale(1.05);
}
/* HubSpot compatibility: Removed 0% and specified only 100% */
@keyframes logoSlide {
  100% {
    transform: translateX(-50%);
  }
}
/* Pause on hover */
.logo-slider-wrapper:hover .logo-slider-track {
  animation-play-state: paused;
}
/* Responsive */
@media (max-width: 768px) {
  .logo-slider-track {
    animation-duration: 30s;
  }

  .logo-slider-item {
    width: 150px;
    height: 75px;
    margin-right: 40px;
    padding: 10px;
  }

  .logo-slider-wrapper {
    padding: 30px 0;
  }
}
@media (max-width: 480px) {
  .logo-slider-track {
    animation-duration: 35s;
  }

  .logo-slider-item {
    width: 120px;
    height: 60px;
    margin-right: 30px;
    padding: 8px;
  }

  .logo-slider-wrapper {
    padding: 20px 0;
  }
}
/* Accessibility support */
@media (prefers-reduced-motion: reduce) {
  .logo-slider-track {
    animation: none;
  }
}

Javascript
// Animation with JavaScript (Alternative approach)
document.addEventListener('DOMContentLoaded', function() {
  var logoSliders = document.querySelectorAll('.logo-slider-wrapper');

  logoSliders.forEach(function(slider) {
    var track = slider.querySelector('.logo-slider-track');
    if (!track) return;

    var startTime = null;
    var duration = 25000; // 25 seconds

    function animate(timestamp) {
      if (!startTime) startTime = timestamp;
      var progress = (timestamp - startTime) % duration;
      var percentage = (progress / duration) * 50; // Move 50%

      track.style.transform = 'translateX(-' + percentage + '%)';
      requestAnimationFrame(animate);
    }

    requestAnimationFrame(animate);
  });
});