HubSpotモジュール:HubSpot 導入インタビュー・事例紹介モジュール要件書

B2Bの導入事例や顧客のポートフォリオ、成功事例などを、レスポンシブ対応のインタラクティブなスライダー形式で紹介するためのモジュールです。編集者は画像、タグ、タイトル、企業名、リンク付きの事例カードを簡単に追加できます。

Demo Video

Detail

導入事例スライダーモジュール

複数の導入事例や実績を、省スペースかつ魅力的なスライダー形式で表示するためのモジュールです。

利用目的

  • B2Bクライアントの成功事例や実績を、分かりやすく紹介する。
  • 様々な企業との実績を提示し、信頼性や説得力を高める。
  • 詳細な事例紹介ページへ効果的に誘導する。

実現できること

  • 導入事例の「カード」を、必要な数だけ追加・削除・並べ替えできる。
  • 各カードに、画像、タイトル、企業名、複数のタグ(例:業界、利用サービス)、詳細ページへのリンクを設定可能。
  • モジュール全体のプライマリーカラーや、メインのタイトル、カードのタイトル、企業名、タグのフォントサイズを変更できる。
  • レスポンシブ対応で、PC・タブレット・スマートフォンで表示されるカード数が自動で最適化される。
  • ボタン、ページネーション(点)、マウスドラッグ、タッチスワイプによる直感的な操作が可能。

実装におけるポイント・注意点

  • リピーターフィールド: このモジュールは、「導入事例」の項目に 繰り返し フィールドを使用しています。そのグループ内に、画像 フィールド、テキスト フィールド(タイトル)、テキスト フィールド(企業名)、タグ用の 繰り返し フィールド、リンク フィールドを1セットとして構成します。
  • スタイル設定: カラーピッカーprimary_color用)や 数値 フィールド(フォントサイズ用)を用意することで、編集者がコードを触らずにデザインを調整できるようにします。
  • JavaScriptの依存: 添付のJavaScriptがスライダーの全ロジック(レスポンシブ対応、各種操作方法)を管理します。モジュールが機能するために、JSが正しく読み込まれるようにしてください。
  • 画像サイズ: 編集者の混乱を防ぐため、画像フィールドのヘルプテキストで、すべての事例画像に一貫したアスペクト比(例:16:9)を推奨します。CSSの object-fit: cover が多少の差異は吸収しますが、デザインの統一感を保つためにはアスペクト比を揃えるのが望ましいです。

🛠 実装におけるポイント・注意点

フィールド構成

  • セクション設定: メインタイトル(例:「導入インタビュー」)
  • リピーターグループ: 事例カード用の繰り返しフィールド
    • 画像フィールド(推奨サイズ: 600x400px、事例の様子やロゴ)
    • テキストフィールド(事例タイトル・成果概要)
    • テキストフィールド(会社名・組織名)
    • URLフィールド(詳細記事・インタビューページへのリンク)
    • タググループ(複数設定可能)
      • テキストフィールド(成果タグ:「アポ獲得率2倍」など)
[
  {
    "name": "main_title",
    "label": "メインタイトル",
    "type": "text",
    "default": "導入インタビュー"
  },
  {
    "name": "primary_color",
    "label": "プライマリーカラー",
    "type": "color",
    "default": {
      "color": "#007bff"
    }
  },
  {
    "name": "title_font_size",
    "label": "タイトルフォントサイズ(px)",
    "type": "number",
    "default": 32
  },
  {
    "name": "company_font_size",
    "label": "会社名フォントサイズ(px)",
    "type": "number",
    "default": 14
  },
  {
    "name": "description_font_size",
    "label": "説明文フォントサイズ(px)",
    "type": "number",
    "default": 16
  },
  {
    "name": "tag_font_size",
    "label": "タグフォントサイズ(px)",
    "type": "number",
    "default": 12
  },
  {
    "name": "case_studies",
    "label": "事例",
    "type": "group",
    "expanded": true,
    "occurrence": {
      "min": 1,
      "max": 20,
      "default": 3
    },
    "children": [
      {
        "name": "image",
        "label": "画像",
        "type": "image",
        "required": false,
        "resizable": true
      },
      {
        "name": "title",
        "label": "タイトル",
        "type": "text",
        "default": "事例のタイトルを入力してください",
        "required": true
      },
      {
        "name": "company_name",
        "label": "会社名",
        "type": "text",
        "default": "会社名を入力してください",
        "required": true
      },
      {
        "name": "link_url",
        "label": "リンク先URL",
        "type": "url",
        "required": false
      },
      {
        "name": "tags",
        "label": "タグ",
        "type": "group",
        "occurrence": {
          "min": 0,
          "max": 10,
          "default": 2
        },
        "children": [
          {
            "name": "tag_text",
            "label": "タグテキスト",
            "type": "text",
            "required": true,
            "help_text": "表示したいタグのテキストを入力してください"
          }
        ]
      }
    ]
  }
]

スタイリング設定

  • カラーフィールド: プライマリーカラー(タグ背景、ボタン色)
  • 数値フィールド: 各要素のフォントサイズ(タイトル、会社名、説明文、タグ)
  • レイアウト制御: CSS Grid/Flexboxによる自動レスポンシブ対応

技術的考慮事項

  • スライダー機能: Vanilla JavaScriptで実装、外部ライブラリに依存しない軽量設計
  • 操作性: 矢印ボタン、ドットナビゲーション、ドラッグ&スワイプ操作
  • パフォーマンス: 画像の遅延読み込み、スムーズなアニメーション
  • アクセシビリティ: キーボードナビゲーション、スクリーンリーダー対応
  • SEO対策: 構造化データ(Review Schema)の出力オプション

📝 使用例

  1. BtoBサービスの導入事例: 「売上300%向上」「業務効率化達成」などの成果を強調
  2. SaaSプロダクトの成功事例: 具体的な数値とお客様の声でサービス価値を訴求
  3. コンサルティング実績: プロジェクトの成果と顧客満足度を事例で証明
  4. 教育・研修サービス: 受講生や企業の成長・変化を具体的に紹介

🎨 デザインガイドライン

  • 視覚的インパクト: 成果タグを目立つ色で配置し、数値やメリットを強調
  • 信頼性の演出: 実際の顧客写真やロゴを使用して真正性をアピール
  • 読みやすさ: 適切な余白とフォントサイズで情報を整理
  • 一貫性: 全事例で統一されたフォーマットを維持

🚀 導入効果

  • コンバージョン率向上: 事例による信頼性で問い合わせ・申込み増加
  • 営業資料の効率化: Webサイトの事例を営業プレゼンでも活用可能
  • SEO効果: 顧客企業名での検索流入増加
  • ブランド価値向上: 成功事例の蓄積で業界でのポジション強化
  • 編集効率化: 非技術者でも簡単に新しい事例を追加・更新可能

Source Code

HTML
{% set module_id = name|lower|replace(' ', '_') %}

<div class="case-study-slider" id="{{ module_id }}" 
     style="--primary-color: {{ module.primary_color.color }}; 
            --title-font-size: {{ module.title_font_size }}px;
            --company-font-size: {{ module.company_font_size }}px;
            --description-font-size: {{ module.description_font_size }}px;
            --tag-font-size: {{ module.tag_font_size }}px;">
  
  {% if module.main_title %}
    <h2 class="slider-title">{{ module.main_title }}</h2>
  {% endif %}

  <div class="slider-container">
    <div class="slider-track" id="{{ module_id }}_track">
      {% for case in module.case_studies %}
        <div class="case-card">
          {% if case.image and case.image.src %}
            <div class="case-image">
              <img src="{{ case.image.src }}" alt="{{ case.image.alt or case.title }}" loading="lazy">
            </div>
          {% else %}
            {# デバッグ用:画像がない場合のプレースホルダー #}
            <div class="case-image placeholder">
              <div class="placeholder-content">No Image</div>
            </div>
          {% endif %}
          
          <div class="case-content">
            {# タグの表示 #}
            {% if case.tags and case.tags|length > 0 %}
              <div class="tags-container">
                {% for tag in case.tags %}
                  {% if tag.tag_text %}
                    <span class="tag">{{ tag.tag_text }}</span>
                  {% endif %}
                {% endfor %}
              </div>
            {% endif %}
            
            {% if case.title %}
              <h3 class="case-title">{{ case.title }}</h3>
            {% endif %}
            
            {% if case.company_name %}
              <p class="company-name">{{ case.company_name }}</p>
            {% endif %}
            
            {% if case.link_url and case.link_url.href %}
              <a href="{{ case.link_url.href }}" class="case-link"
                 {% if case.link_url.open_in_new_tab %}target="_blank"{% endif %}>
                <svg class="arrow-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <path d="M5 12h14M12 5l7 7-7 7"/>
                </svg>
              </a>
            {% endif %}
          </div>
        </div>
      {% endfor %}
    </div>
    
    <div class="slider-controls">
      <button class="prev-btn" data-target="{{ module_id }}_track" aria-label="前のスライド">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M19 12H5M12 19l-7-7 7-7"/>
        </svg>
      </button>
      
      <div class="pagination">
        {% for case in module.case_studies %}
          <button class="pagination-dot{% if loop.first %} active{% endif %}" 
                  data-slide="{{ loop.index0 }}" data-target="{{ module_id }}_track"></button>
        {% endfor %}
      </div>
      
      <button class="next-btn" data-target="{{ module_id }}_track" aria-label="次のスライド">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M5 12h14M12 5l7 7-7 7"/>
        </svg>
      </button>
    </div>
  </div>
</div>

CSS
.case-study-slider {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

.slider-title {
  font-size: var(--title-font-size, 32px);
  font-weight: bold;
  margin-bottom: 40px;
  text-align: left;
  color: var(--primary-color, #333);
}

.slider-container {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
}

.slider-track {
  display: flex;
  transition: transform 0.3s ease;
  gap: 20px;
}

.case-card {
  flex: 0 0 calc(33.333% - 14px);
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.case-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}

.case-image.placeholder {
  background-color: #f5f5f5;
  display: flex;
  align-items: center;
  justify-content: center;
}

.placeholder-content {
  color: #999;
  font-size: 14px;
}

.case-image {
  position: relative;
  height: 200px;
  overflow: hidden;
}

.case-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}

.case-card:hover .case-image img {
  transform: scale(1.05);
}

.case-content {
  padding: 24px;
  position: relative;
}

.tags-container {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

.tag {
  background-color: var(--primary-color, #007bff);
  color: white;
  padding: 6px 12px;
  border-radius: 20px;
  font-size: var(--tag-font-size, 12px);
  font-weight: 500;
  white-space: nowrap;
}

.case-title {
  font-size: var(--description-font-size, 16px);
  font-weight: 600;
  line-height: 1.5;
  margin-bottom: 16px;
  color: #333;
  min-height: 48px;
}

.company-name {
  font-size: var(--company-font-size, 14px);
  color: #666;
  margin-bottom: 20px;
  font-weight: 500;
}

.case-link {
  position: absolute;
  bottom: 20px;
  right: 20px;
  width: 40px;
  height: 40px;
  background-color: var(--primary-color, #007bff);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  transition: all 0.3s ease;
}

.case-link:hover {
  transform: scale(1.1);
  background-color: color-mix(in srgb, var(--primary-color, #007bff) 80%, black);
}

.arrow-icon {
  width: 20px;
  height: 20px;
  color: white;
}

.slider-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin-top: 30px;
}

.prev-btn, .next-btn {
  width: 48px;
  height: 48px;
  border: 2px solid var(--primary-color, #007bff);
  background: transparent;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
  color: var(--primary-color, #007bff);
}

.prev-btn:hover, .next-btn:hover {
  background-color: var(--primary-color, #007bff);
  color: white;
  transform: scale(1.05);
}

.prev-btn:disabled, .next-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.prev-btn:disabled:hover, .next-btn:disabled:hover {
  background: transparent;
  color: var(--primary-color, #007bff);
  transform: none;
}

.prev-btn svg, .next-btn svg {
  width: 20px;
  height: 20px;
}

.pagination {
  display: flex;
  gap: 8px;
}

.pagination-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: none;
  background-color: #ddd;
  cursor: pointer;
  transition: all 0.3s ease;
}

.pagination-dot.active {
  background-color: var(--primary-color, #007bff);
}

.pagination-dot:hover {
  background-color: var(--primary-color, #007bff);
  opacity: 0.7;
}

/* レスポンシブデザイン */
@media (max-width: 768px) {
  .case-card {
    flex: 0 0 calc(50% - 10px);
  }
  
  .slider-title {
    font-size: calc(var(--title-font-size, 32px) * 0.8);
  }
}

@media (max-width: 480px) {
  .case-card {
    flex: 0 0 calc(100% - 0px);
  }
  
  .slider-track {
    gap: 10px;
  }
  
  .case-content {
    padding: 20px;
  }
  
  .slider-title {
    font-size: calc(var(--title-font-size, 32px) * 0.7);
    margin-bottom: 30px;
  }
}

Javascript
document.addEventListener('DOMContentLoaded', function() {
  // すべてのスライダーを初期化
  const sliders = document.querySelectorAll('.case-study-slider');
  
  sliders.forEach(function(slider) {
    initializeSlider(slider);
  });
  
  function initializeSlider(sliderElement) {
    const track = sliderElement.querySelector('.slider-track');
    const prevBtn = sliderElement.querySelector('.prev-btn');
    const nextBtn = sliderElement.querySelector('.next-btn');
    const paginationDots = sliderElement.querySelectorAll('.pagination-dot');
    const cards = sliderElement.querySelectorAll('.case-card');
    
    if (!track || cards.length === 0) return;
    
    let currentIndex = 0;
    let cardsPerView = getCardsPerView();
    const totalCards = cards.length;
    const maxIndex = Math.max(0, totalCards - cardsPerView);
    
    // ウィンドウサイズに応じて表示カード数を決定
    function getCardsPerView() {
      const width = window.innerWidth;
      if (width <= 480) return 1;
      if (width <= 768) return 2;
      return 3;
    }
    
    // スライダーの位置を更新
    function updateSliderPosition() {
      const cardWidth = cards[0].offsetWidth;
      const gap = 20;
      const translateX = -(currentIndex * (cardWidth + gap));
      track.style.transform = `translateX(${translateX}px)`;
      
      // ボタンの状態を更新
      if (prevBtn) prevBtn.disabled = currentIndex === 0;
      if (nextBtn) nextBtn.disabled = currentIndex >= maxIndex;
      
      // ページネーションドットの状態を更新
      paginationDots.forEach(function(dot, index) {
        dot.classList.toggle('active', index === currentIndex);
      });
    }
    
    // 前のスライドに移動
    function goToPrevSlide() {
      if (currentIndex > 0) {
        currentIndex--;
        updateSliderPosition();
      }
    }
    
    // 次のスライドに移動
    function goToNextSlide() {
      if (currentIndex < maxIndex) {
        currentIndex++;
        updateSliderPosition();
      }
    }
    
    // 特定のスライドに移動
    function goToSlide(index) {
      currentIndex = Math.min(Math.max(0, index), maxIndex);
      updateSliderPosition();
    }
    
    // イベントリスナーを設定
    if (prevBtn) {
      prevBtn.addEventListener('click', goToPrevSlide);
    }
    
    if (nextBtn) {
      nextBtn.addEventListener('click', goToNextSlide);
    }
    
    paginationDots.forEach(function(dot, index) {
      dot.addEventListener('click', function() {
        goToSlide(index);
      });
    });
    
    // キーボードナビゲーション
    sliderElement.addEventListener('keydown', function(e) {
      if (e.key === 'ArrowLeft') {
        goToPrevSlide();
        e.preventDefault();
      } else if (e.key === 'ArrowRight') {
        goToNextSlide();
        e.preventDefault();
      }
    });
    
    // タッチ/スワイプサポート
    let startX = 0;
    let isDragging = false;
    
    track.addEventListener('touchstart', function(e) {
      startX = e.touches[0].clientX;
      isDragging = true;
    });
    
    track.addEventListener('touchmove', function(e) {
      if (!isDragging) return;
      e.preventDefault();
    });
    
    track.addEventListener('touchend', function(e) {
      if (!isDragging) return;
      
      const endX = e.changedTouches[0].clientX;
      const diff = startX - endX;
      
      if (Math.abs(diff) > 50) {
        if (diff > 0) {
          goToNextSlide();
        } else {
          goToPrevSlide();
        }
      }
      
      isDragging = false;
    });
    
    // マウスドラッグサポート
    track.addEventListener('mousedown', function(e) {
      startX = e.clientX;
      isDragging = true;
      track.style.cursor = 'grabbing';
      e.preventDefault();
    });
    
    document.addEventListener('mousemove', function(e) {
      if (!isDragging) return;
      e.preventDefault();
    });
    
    document.addEventListener('mouseup', function(e) {
      if (!isDragging) return;
      
      const endX = e.clientX;
      const diff = startX - endX;
      
      if (Math.abs(diff) > 50) {
        if (diff > 0) {
          goToNextSlide();
        } else {
          goToPrevSlide();
        }
      }
      
      isDragging = false;
      track.style.cursor = 'grab';
    });
    
    // リサイズ時の再計算
    window.addEventListener('resize', function() {
      const newCardsPerView = getCardsPerView();
      if (newCardsPerView !== cardsPerView) {
        cardsPerView = newCardsPerView;
        currentIndex = Math.min(currentIndex, Math.max(0, totalCards - cardsPerView));
        updateSliderPosition();
      }
    });
    
    // 初期化
    updateSliderPosition();
    track.style.cursor = 'grab';
  }
});