カルーセル / スライダー
自動再生・矢印・ドット・スワイプに対応した軽量カルーセル。translateXによる横移動で、バナーや特集の回遊表示に使えます。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- MOON BREW:季節のおすすめを回遊させるカルーセル -->
<div class="cafe">
<div class="cafe__bar">
<span class="cafe__logo">☕ MOON BREW</span>
<span class="cafe__tag">SEASONAL</span>
</div>
<div class="carousel" data-carousel>
<div class="carousel__viewport">
<div class="carousel__track">
<div class="slide" style="background-image:url('https://picsum.photos/640/300?random=11')">
<div class="slide__copy">
<span class="slide__pill">秋限定</span>
<h2 class="slide__title">焙煎マロンラテ</h2>
<p class="slide__sub">深煎り豆と国産栗の甘み。湯気まで香ばしい一杯。</p>
</div>
</div>
<div class="slide" style="background-image:url('https://picsum.photos/640/300?random=12')">
<div class="slide__copy">
<span class="slide__pill">新発売</span>
<h2 class="slide__title">水出しコールドブリュー</h2>
<p class="slide__sub">18時間かけて低温抽出。すっきり澄んだ後味。</p>
</div>
</div>
<div class="slide" style="background-image:url('https://picsum.photos/640/300?random=13')">
<div class="slide__copy">
<span class="slide__pill">週末限定</span>
<h2 class="slide__title">焼きたてスコーンセット</h2>
<p class="slide__sub">お好きなドリンクと自家製スコーンで¥780。</p>
</div>
</div>
</div>
</div>
<button class="nav nav--prev" data-prev aria-label="前へ">‹</button>
<button class="nav nav--next" data-next aria-label="次へ">›</button>
<div class="dots" data-dots></div>
</div>
</div>
CSS
/* MOON BREW カフェ テーマ */
:root{--cream:#f5ede1;--brown:#2b1d12;--amber:#c98a3b}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;display:grid;place-items:center;padding:14px;
font-family:"Hiragino Mincho ProN","Segoe UI",serif;
background:var(--cream);color:var(--brown);
}
.cafe{width:min(560px,100%)}
.cafe__bar{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.cafe__logo{font-weight:700;letter-spacing:.06em}
.cafe__tag{font-size:.72rem;letter-spacing:.25em;color:var(--amber)}
.carousel{position:relative;border-radius:16px;overflow:hidden;box-shadow:0 10px 30px rgba(43,29,18,.18)}
.carousel__viewport{overflow:hidden}
.carousel__track{display:flex;transition:transform .5s cubic-bezier(.4,0,.2,1)}
.slide{
position:relative;flex:0 0 100%;height:300px;
background-size:cover;background-position:center;color:#fff;
}
.slide::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(43,29,18,.78),rgba(43,29,18,.05))}
.slide__copy{position:relative;z-index:1;max-width:60%;padding:34px 30px}
.slide__pill{
display:inline-block;padding:4px 12px;border-radius:999px;
background:var(--amber);color:#2b1d12;font-size:.7rem;font-weight:700;letter-spacing:.14em;
}
.slide__title{margin:12px 0 8px;font-size:1.6rem;letter-spacing:.04em}
.slide__sub{margin:0;font-size:.9rem;line-height:1.7;opacity:.92}
.nav{
position:absolute;top:50%;transform:translateY(-50%);
width:38px;height:38px;border:none;border-radius:50%;cursor:pointer;
background:rgba(255,255,255,.85);color:#2b1d12;font-size:1.4rem;line-height:1;
display:grid;place-items:center;transition:background .2s;
}
.nav:hover{background:#fff}
.nav--prev{left:12px}
.nav--next{right:12px}
.dots{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);display:flex;gap:8px}
.dot{width:8px;height:8px;border:none;border-radius:50%;background:rgba(255,255,255,.5);cursor:pointer;padding:0}
.dot.is-active{background:var(--amber);transform:scale(1.3)}
@media (prefers-reduced-motion:reduce){.carousel__track{transition:none}}
JavaScript
// 自動再生・矢印・ドット・スワイプ対応カルーセル
const root = document.querySelector('[data-carousel]');
if (root) {
const track = root.querySelector('.carousel__track');
const slides = [...root.querySelectorAll('.slide')];
const dotsBox = root.querySelector('[data-dots]');
let index = 0;
let timer = null;
const INTERVAL = 3800;
// ドット生成
slides.forEach((_, i) => {
const dot = document.createElement('button');
dot.className = 'dot';
dot.setAttribute('aria-label', `${i + 1}枚目へ`);
dot.addEventListener('click', () => { go(i); restart(); });
dotsBox.appendChild(dot);
});
const dots = [...dotsBox.children];
// 指定スライドへ移動
const go = (i) => {
index = (i + slides.length) % slides.length;
track.style.transform = `translateX(-${index * 100}%)`;
dots.forEach((d, di) => d.classList.toggle('is-active', di === index));
};
const next = () => go(index + 1);
const prev = () => go(index - 1);
root.querySelector('[data-next]')?.addEventListener('click', () => { next(); restart(); });
root.querySelector('[data-prev]')?.addEventListener('click', () => { prev(); restart(); });
// 自動再生(reduce-motion 時は止める)
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = () => { if (!reduce) timer = setInterval(next, INTERVAL); };
const stop = () => { clearInterval(timer); timer = null; };
const restart = () => { stop(); start(); };
root.addEventListener('mouseenter', stop);
root.addEventListener('mouseleave', start);
// ポインタによるスワイプ
let startX = null;
const vp = root.querySelector('.carousel__viewport');
vp.addEventListener('pointerdown', (e) => { startX = e.clientX; stop(); });
vp.addEventListener('pointerup', (e) => {
if (startX === null) return;
const dx = e.clientX - startX;
if (Math.abs(dx) > 40) (dx < 0 ? next : prev)();
startX = null;
start();
});
go(0);
start();
}
コード
HTML
<!-- カルーセル:自動再生・矢印・ドット・スワイプ対応のスライダー -->
<div class="carousel" data-carousel>
<div class="carousel__viewport">
<ul class="carousel__track">
<li class="slide" style="--from:#6366f1;--to:#ec4899">
<span class="slide__num">01</span>
<h3>新しい体験をデザインする</h3>
<p>滑らかな移動とスワイプ操作に対応した軽量カルーセル。</p>
</li>
<li class="slide" style="--from:#0ea5e9;--to:#22d3ee">
<span class="slide__num">02</span>
<h3>自動再生とホバー停止</h3>
<p>マウスを乗せると一時停止。離すと再び動き出します。</p>
</li>
<li class="slide" style="--from:#f59e0b;--to:#ef4444">
<span class="slide__num">03</span>
<h3>ドットでジャンプ</h3>
<p>インジケーターをクリックして任意のスライドへ。</p>
</li>
<li class="slide" style="--from:#10b981;--to:#84cc16">
<span class="slide__num">04</span>
<h3>レスポンシブ対応</h3>
<p>幅に応じて自動でフィット。外部ライブラリ不要。</p>
</li>
</ul>
</div>
<button class="carousel__arrow carousel__arrow--prev" data-prev aria-label="前へ">❮</button>
<button class="carousel__arrow carousel__arrow--next" data-next aria-label="次へ">❯</button>
<div class="carousel__dots" data-dots></div>
</div>
CSS
:root{
--bg:#0b1220;
--text:#fff;
--radius:18px;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:24px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:radial-gradient(700px 360px at 50% -10%,#1a2540,transparent),var(--bg);
}
.carousel{
position:relative;width:min(480px,100%);
border-radius:var(--radius);
box-shadow:0 30px 60px -28px rgba(0,0,0,.7);
}
.carousel__viewport{overflow:hidden;border-radius:var(--radius)}
/* トラックを translateX でスライド */
.carousel__track{
display:flex;margin:0;padding:0;list-style:none;
transition:transform .5s cubic-bezier(.4,0,.2,1);
}
.slide{
flex:0 0 100%;min-height:240px;
display:flex;flex-direction:column;justify-content:center;gap:8px;
padding:34px 30px;
background:linear-gradient(135deg,var(--from),var(--to));
}
.slide__num{
font-size:2.4rem;font-weight:800;opacity:.35;line-height:1;
}
.slide h3{margin:6px 0 0;font-size:1.4rem}
.slide p{margin:0;max-width:30ch;line-height:1.6;opacity:.92;font-size:.92rem}
/* 矢印 */
.carousel__arrow{
position:absolute;top:50%;transform:translateY(-50%);
width:42px;height:42px;border:none;border-radius:50%;cursor:pointer;
background:rgba(0,0,0,.35);color:#fff;font-size:1rem;
display:grid;place-items:center;
backdrop-filter:blur(4px);transition:background .2s,transform .15s;
}
.carousel__arrow:hover{background:rgba(0,0,0,.6)}
.carousel__arrow:active{transform:translateY(-50%) scale(.92)}
.carousel__arrow--prev{left:12px}
.carousel__arrow--next{right:12px}
/* ドット */
.carousel__dots{
position:absolute;left:0;right:0;bottom:14px;
display:flex;justify-content:center;gap:8px;
}
.dot{
width:9px;height:9px;border-radius:50%;border:none;cursor:pointer;padding:0;
background:rgba(255,255,255,.45);transition:width .3s,background .3s;
}
.dot.is-active{width:22px;border-radius:6px;background:#fff}
@media (prefers-reduced-motion:reduce){.carousel__track{transition:none}}
JavaScript
// 自動再生・矢印・ドット・スワイプ対応カルーセル
const root = document.querySelector('[data-carousel]');
if (root) {
const track = root.querySelector('.carousel__track');
const slides = [...root.querySelectorAll('.slide')];
const dotsBox = root.querySelector('[data-dots]');
let index = 0;
let timer = null;
const INTERVAL = 3500;
// ドット生成
slides.forEach((_, i) => {
const dot = document.createElement('button');
dot.className = 'dot';
dot.setAttribute('aria-label', `${i + 1}枚目へ`);
dot.addEventListener('click', () => go(i));
dotsBox.appendChild(dot);
});
const dots = [...dotsBox.children];
// 指定スライドへ移動
const go = (i) => {
index = (i + slides.length) % slides.length;
track.style.transform = `translateX(-${index * 100}%)`;
dots.forEach((d, di) => d.classList.toggle('is-active', di === index));
};
const next = () => go(index + 1);
const prev = () => go(index - 1);
root.querySelector('[data-next]')?.addEventListener('click', () => { next(); restart(); });
root.querySelector('[data-prev]')?.addEventListener('click', () => { prev(); restart(); });
// 自動再生(reduce-motion 時は止める)
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = () => { if (!reduce) timer = setInterval(next, INTERVAL); };
const stop = () => { clearInterval(timer); timer = null; };
const restart = () => { stop(); start(); };
root.addEventListener('mouseenter', stop);
root.addEventListener('mouseleave', start);
// タッチ/ポインタによるスワイプ
let startX = null;
const vp = root.querySelector('.carousel__viewport');
vp.addEventListener('pointerdown', (e) => { startX = e.clientX; stop(); });
vp.addEventListener('pointerup', (e) => {
if (startX === null) return;
const dx = e.clientX - startX;
if (Math.abs(dx) > 40) (dx < 0 ? next : prev)();
startX = null;
start();
});
go(0);
start();
}
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「カルーセル / スライダー」の効果を追加してください。
# 追加してほしい効果
カルーセル / スライダー(UIコンポーネント)
自動再生・矢印・ドット・スワイプに対応した軽量カルーセル。translateXによる横移動で、バナーや特集の回遊表示に使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- カルーセル:自動再生・矢印・ドット・スワイプ対応のスライダー -->
<div class="carousel" data-carousel>
<div class="carousel__viewport">
<ul class="carousel__track">
<li class="slide" style="--from:#6366f1;--to:#ec4899">
<span class="slide__num">01</span>
<h3>新しい体験をデザインする</h3>
<p>滑らかな移動とスワイプ操作に対応した軽量カルーセル。</p>
</li>
<li class="slide" style="--from:#0ea5e9;--to:#22d3ee">
<span class="slide__num">02</span>
<h3>自動再生とホバー停止</h3>
<p>マウスを乗せると一時停止。離すと再び動き出します。</p>
</li>
<li class="slide" style="--from:#f59e0b;--to:#ef4444">
<span class="slide__num">03</span>
<h3>ドットでジャンプ</h3>
<p>インジケーターをクリックして任意のスライドへ。</p>
</li>
<li class="slide" style="--from:#10b981;--to:#84cc16">
<span class="slide__num">04</span>
<h3>レスポンシブ対応</h3>
<p>幅に応じて自動でフィット。外部ライブラリ不要。</p>
</li>
</ul>
</div>
<button class="carousel__arrow carousel__arrow--prev" data-prev aria-label="前へ">❮</button>
<button class="carousel__arrow carousel__arrow--next" data-next aria-label="次へ">❯</button>
<div class="carousel__dots" data-dots></div>
</div>
【CSS】
:root{
--bg:#0b1220;
--text:#fff;
--radius:18px;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:24px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:radial-gradient(700px 360px at 50% -10%,#1a2540,transparent),var(--bg);
}
.carousel{
position:relative;width:min(480px,100%);
border-radius:var(--radius);
box-shadow:0 30px 60px -28px rgba(0,0,0,.7);
}
.carousel__viewport{overflow:hidden;border-radius:var(--radius)}
/* トラックを translateX でスライド */
.carousel__track{
display:flex;margin:0;padding:0;list-style:none;
transition:transform .5s cubic-bezier(.4,0,.2,1);
}
.slide{
flex:0 0 100%;min-height:240px;
display:flex;flex-direction:column;justify-content:center;gap:8px;
padding:34px 30px;
background:linear-gradient(135deg,var(--from),var(--to));
}
.slide__num{
font-size:2.4rem;font-weight:800;opacity:.35;line-height:1;
}
.slide h3{margin:6px 0 0;font-size:1.4rem}
.slide p{margin:0;max-width:30ch;line-height:1.6;opacity:.92;font-size:.92rem}
/* 矢印 */
.carousel__arrow{
position:absolute;top:50%;transform:translateY(-50%);
width:42px;height:42px;border:none;border-radius:50%;cursor:pointer;
background:rgba(0,0,0,.35);color:#fff;font-size:1rem;
display:grid;place-items:center;
backdrop-filter:blur(4px);transition:background .2s,transform .15s;
}
.carousel__arrow:hover{background:rgba(0,0,0,.6)}
.carousel__arrow:active{transform:translateY(-50%) scale(.92)}
.carousel__arrow--prev{left:12px}
.carousel__arrow--next{right:12px}
/* ドット */
.carousel__dots{
position:absolute;left:0;right:0;bottom:14px;
display:flex;justify-content:center;gap:8px;
}
.dot{
width:9px;height:9px;border-radius:50%;border:none;cursor:pointer;padding:0;
background:rgba(255,255,255,.45);transition:width .3s,background .3s;
}
.dot.is-active{width:22px;border-radius:6px;background:#fff}
@media (prefers-reduced-motion:reduce){.carousel__track{transition:none}}
【JavaScript】
// 自動再生・矢印・ドット・スワイプ対応カルーセル
const root = document.querySelector('[data-carousel]');
if (root) {
const track = root.querySelector('.carousel__track');
const slides = [...root.querySelectorAll('.slide')];
const dotsBox = root.querySelector('[data-dots]');
let index = 0;
let timer = null;
const INTERVAL = 3500;
// ドット生成
slides.forEach((_, i) => {
const dot = document.createElement('button');
dot.className = 'dot';
dot.setAttribute('aria-label', `${i + 1}枚目へ`);
dot.addEventListener('click', () => go(i));
dotsBox.appendChild(dot);
});
const dots = [...dotsBox.children];
// 指定スライドへ移動
const go = (i) => {
index = (i + slides.length) % slides.length;
track.style.transform = `translateX(-${index * 100}%)`;
dots.forEach((d, di) => d.classList.toggle('is-active', di === index));
};
const next = () => go(index + 1);
const prev = () => go(index - 1);
root.querySelector('[data-next]')?.addEventListener('click', () => { next(); restart(); });
root.querySelector('[data-prev]')?.addEventListener('click', () => { prev(); restart(); });
// 自動再生(reduce-motion 時は止める)
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = () => { if (!reduce) timer = setInterval(next, INTERVAL); };
const stop = () => { clearInterval(timer); timer = null; };
const restart = () => { stop(); start(); };
root.addEventListener('mouseenter', stop);
root.addEventListener('mouseleave', start);
// タッチ/ポインタによるスワイプ
let startX = null;
const vp = root.querySelector('.carousel__viewport');
vp.addEventListener('pointerdown', (e) => { startX = e.clientX; stop(); });
vp.addEventListener('pointerup', (e) => {
if (startX === null) return;
const dx = e.clientX - startX;
if (Math.abs(dx) > 40) (dx < 0 ? next : prev)();
startX = null;
start();
});
go(0);
start();
}
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。