B2Bの導入事例や顧客のポートフォリオ、成功事例などを、レスポンシブ対応のインタラクティブなスライダー形式で紹介するためのモジュールです。編集者は画像、タグ、タイトル、企業名、リンク付きの事例カードを簡単に追加できます。
複数の導入事例や実績を、省スペースかつ魅力的なスライダー形式で表示するためのモジュールです。
利用目的
実現できること
実装におけるポイント・注意点
繰り返し
フィールドを使用しています。そのグループ内に、画像
フィールド、テキスト
フィールド(タイトル)、テキスト
フィールド(企業名)、タグ用の 繰り返し
フィールド、リンク
フィールドを1セットとして構成します。カラーピッカー
(primary_color
用)や 数値
フィールド(フォントサイズ用)を用意することで、編集者がコードを触らずにデザインを調整できるようにします。object-fit: cover
が多少の差異は吸収しますが、デザインの統一感を保つためにはアスペクト比を揃えるのが望ましいです。[
{
"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": "表示したいタグのテキストを入力してください"
}
]
}
]
}
]
{% 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>
.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;
}
}
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';
}
});