アニメーション
【プロンプト有】AIがCSSとJSで作るメインが浮き上がる奥行き演出のスライダー
2026/04/16
2026/4/13
スライダーUIは多くのサイトで使われていますが、単純な横スクロールでは印象に残りにくいこともあります。
この記事では、AIを活用して生成したコードをベースに、CSSとJavaScriptだけで実装できる奥行き演出付きの3Dスライダーを紹介します。
カードが前後に重なりながら動くことで立体感を生み出し、ポートフォリオやLPのメインビジュアルにも使えるインタラクティブな表現を実現します。
Preview プレビュー
Code コード
<div class="background-spotlight"></div>
<div class="carousel-container" id="carousel">
<div class="card-stack" id="cardStack">
</div>
<div class="nav-dots" id="navDots">
</div>
</div>
:root {
--card-width: 320px;
--card-height: 450px;
--active-scale: 1.1;
--inactive-scale: 0.85;
--spacing: 160px;
--rotation: 25deg;
--transition-speed: 0.6s;
--bg-color: #050505;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg-color);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
font-family: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
color: #fff;
}
.background-spotlight {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 50% 40%, rgba(60, 60, 80, 0.4) 0%, rgba(5, 5, 5, 1) 70%);
z-index: -1;
filter: blur(40px);
}
.carousel-container {
position: relative;
width: 100%;
height: 600px;
perspective: 1500px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
user-select: none;
}
.card-stack {
position: relative;
width: var(--card-width);
height: var(--card-height);
transform-style: preserve-3d;
cursor: grab;
}
.card-stack:active {
cursor: grabbing;
}
.card {
position: absolute;
width: 100%;
height: 100%;
border-radius: 20px;
overflow: hidden;
transition: transform var(--transition-speed) cubic-bezier(0.25, 1, 0.5, 1),
opacity var(--transition-speed),
filter var(--transition-speed);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
background: #1a1a1a;
pointer-events: none;
}
.card.active {
pointer-events: auto;
}
.card img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.card-overlay {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 60%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.4) 50%, transparent 100%);
padding: 24px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.card-title {
font-size: 1.25rem;
font-weight: bold;
margin-bottom: 8px;
line-height: 1.2;
}
.card-description {
font-size: 0.9rem;
opacity: 0.8;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
}
.external-link-icon {
position: absolute;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
padding: 8px;
border-radius: 50%;
opacity: 0;
transition: opacity 0.3s;
text-decoration: none;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
.card.active .external-link-icon {
opacity: 1;
}
.nav-dots {
margin-top: 50px;
display: flex;
gap: 12px;
z-index: 10;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
cursor: pointer;
transition: all 0.3s ease;
}
.dot.active {
background: #fff;
transform: scale(1.5);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
@media (max-width: 768px) {
:root {
--card-width: 260px;
--card-height: 380px;
--spacing: 100px;
}
}
const cardData = [{
id: 1,
title: '静寂のラグジュアリー',
description: '心地よい時間を生み出す上質な空間体験',
imageSrc: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&q=80&w=800',
href: '#'
}, {
id: 2,
title: '洗練されたライフスタイル',
description: '日常をより美しく彩るデザイン思想',
imageSrc: 'https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?auto=format&fit=crop&q=80&w=800',
href: '#'
}, {
id: 3,
title: 'ダイナミックな躍動感',
description: '感性を刺激する力強い存在感',
imageSrc: 'https://images.unsplash.com/photo-1492144534655-ae79c964c9d7?auto=format&fit=crop&q=80&w=800',
href: '#'
}, {
id: 4,
title: '細部へのこだわり',
description: '丁寧に仕上げられた高品質なディテール',
imageSrc: 'https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?auto=format&fit=crop&q=80&w=800',
href: '#'
}, {
id: 5,
title: '新しい価値の創造',
description: '未来志向のアイデアが生む革新的体験',
imageSrc: 'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?auto=format&fit=crop&q=80&w=800',
href: '#'
}];
let currentIndex = 0;
let isDragging = false;
let startX = 0;
let autoPlayTimer = null;
const cardStack = document.getElementById('cardStack');
const navDots = document.getElementById('navDots');
function createUI() {
cardData.forEach((data, index) => {
// Card
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<img src="${data.imageSrc}" alt="${data.title}">
${data.href ? `
<a href="${data.href}" class="external-link-icon" target="_blank">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>
</a>` : ''}
<div class="card-overlay">
<div class="card-title">${data.title}</div>
<div class="card-description">${data.description}</div>
</div>
`;
cardStack.appendChild(card);
// Dot
const dot = document.createElement('div');
dot.className = 'dot';
dot.addEventListener('click', () => goToIndex(index));
navDots.appendChild(dot);
});
updateCarousel();
startAutoPlay();
}
function updateCarousel() {
const cards = document.querySelectorAll('.card');
const dots = document.querySelectorAll('.dot');
const total = cards.length;
cards.forEach((card, i) => {
// 循環インデックス計算(最も近い経路で表示)
let diff = i - currentIndex;
// ループ処理
if (diff > total / 2) diff -= total;
if (diff < -total / 2) diff += total;
const absDiff = Math.abs(diff);
const x = diff * 180; // 水平間隔
const z = absDiff * -150; // 奥行き
const rotateY = diff * -25; // 回転
const scale = i === currentIndex ? 1.1 : 0.85;
const translateY = i === currentIndex ? -30 : 0; // 中央を少し上げる
const opacity = 1 - (absDiff * 0.3);
card.style.transform = `translateX(${x}px) translateZ(${z}px) translateY(${translateY}px) rotateY(${rotateY}deg) scale(${scale})`;
card.style.opacity = opacity;
card.style.zIndex = 100 - absDiff;
card.style.filter = absDiff > 0 ? 'brightness(0.5)' : 'brightness(1)';
card.classList.toggle('active', i === currentIndex);
});
dots.forEach((dot, i) => {
dot.classList.toggle('active', i === currentIndex);
});
}
function goToIndex(index) {
const total = cardData.length;
currentIndex = (index + total) % total;
updateCarousel();
}
function next() {
goToIndex(currentIndex + 1);
}
function prev() {
goToIndex(currentIndex - 1);
}
// ドラッグ・スワイプ処理
cardStack.addEventListener('mousedown', dragStart);
cardStack.addEventListener('touchstart', (e) => dragStart(e.touches[0]));
window.addEventListener('mousemove', dragMove);
window.addEventListener('touchmove', (e) => dragMove(e.touches[0]));
window.addEventListener('mouseup', dragEnd);
window.addEventListener('touchend', dragEnd);
function dragStart(e) {
isDragging = true;
startX = e.clientX;
stopAutoPlay();
}
function dragMove(e) {
if (!isDragging) return;
}
function dragEnd(e) {
if (!isDragging) return;
const endX = e.changedTouches ? e.changedTouches[0].clientX : e.clientX;
const diff = startX - endX;
if (Math.abs(diff) > 50) {
if (diff > 0) next();
else prev();
}
isDragging = false;
startAutoPlay();
}
// キーボード操作
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') next();
if (e.key === 'ArrowLeft') prev();
});
// 自動再生
function startAutoPlay() {
stopAutoPlay();
autoPlayTimer = setInterval(next, 5000);
}
function stopAutoPlay() {
if (autoPlayTimer) clearInterval(autoPlayTimer);
}
const container = document.getElementById('carousel');
container.addEventListener('mouseenter', stopAutoPlay);
container.addEventListener('mouseleave', startAutoPlay);
createUI();
Explanation 詳しい説明
基本構造
このスライダーはHTMLのカード要素を横並びに配置し、それぞれにCSSの3D変形を適用することで奥行き表現を実現しています。
JavaScriptは現在位置の管理とクラスの切り替えのみを担当し、アニメーション自体はCSSトランジションによって滑らかに動作します。
仕様
中央のカードを基準として、前後に配置されたカードへ異なるtransform値を適用します。
translateXによる横移動に加えてscaleやrotate、translateZを組み合わせることで、手前と奥の距離感を視覚的に表現しています。
アクティブなカードは強調表示され、左右のカードは縮小・角度変化によって立体的なスタック構造になります。
カスタム
カード枚数はHTML要素を追加するだけで増減可能です。
奥行きの強さはscale値やtranslateZの数値を調整することで変更できます。
アニメーション速度はtransition-duration、動きの印象はeasingを変更することでサイトデザインに合わせた調整が可能です。また画像・テキスト・ボタンなど自由にコンテンツを配置できます。
AIに指示したプロンプトも掲載しているため、プロンプト内容を調整することで、デザインや動作を柔軟にカスタマイズできます。
注意点
3D表現はtransformを多用するため、親要素にperspectiveを設定しないと奥行きが正しく表示されません。
モバイル環境では過度な回転や拡大縮小が可読性を下げる場合があるため、レスポンシブ時には変形量を控えめにするのがおすすめです。
また、カード数が極端に多い場合は描画負荷が増えるため、表示数を制限するとパフォーマンスを維持できます。
Prompt プロンプト
【基本ルール】
・HTML生成に不要な文章は削除すること
・説明文、区切り線、補足説明、注意書きは出力しない
・実装方法、環境セットアップ、依存関係説明、フォルダ構成の記述は削除する
・出力はコピペ可能な内容のみとする
・余計な装飾や解説を書かない
・生成結果は説明なしでそのまま使用できる状態にすること
【出力形式】
・純粋なHTML / CSS / JavaScriptのみ出力する
・外部フレームワークは禁止
・コード以外の文章を出力しない
以下の仕様に従い、インタラクティブなカードスタックUIをHTML・CSS・JavaScriptのみで実装してください。
目的:
中央のカードをアクティブとして表示し、左右に扇状(ファン状)にカードが並ぶ3D風カルーセルUIを生成する。
UI仕様:
・複数のカードが横方向に重なりながら円弧状に配置される
・中央カードが常に強調表示される
・左右のカードは奥行きと角度を持ち、遠いほど小さく表示される
・カードはスムーズなアニメーションで移動する
・ドラッグ(スワイプ)またはクリックでカード切替可能
・左右矢印キーでも切替可能
・下部にドットナビゲーションを表示する
・ドットクリックで対象カードへ移動できる
・アクティブカードにリンクがある場合、外部リンクアイコンを表示する
・ホバー中は自動切替を一時停止する
レイアウト仕様:
・カードは中央基準で左右対称に配置する
・カード同士は部分的に重なる
・中央カードのみ少し上へ持ち上げて表示する
・非アクティブカードは縮小表示する
・奥行き(Z方向)をCSS transformで表現する
・背景にぼかし付きスポットライト風グラデーションを配置する
・画像はアスペクト比を変更せず、そのまま横長レイアウト内に表示する
・画像を歪ませたり強制トリミングしないこと
アニメーション仕様:
・スプリング風の滑らかな移動アニメーション
・カード切替時に位置・回転・スケールを同時変更
・アクティブカードのみドラッグ操作可能
・一定距離または速度以上のスワイプで次/前へ移動
カードデータ構造:
各カードは以下の情報を持つ:
* id(識別子)
* title(タイトル)
* description(説明文・任意)
* imageSrc(画像URL)
* href(外部リンク・任意)
カード表示内容:
・全面に画像を表示(object-fit: cover または contain を適切に使用)
・下部に暗めのグラデーションオーバーレイを重ねる
・タイトルを太字で表示
・説明文は2行まで表示
動作仕様:
・初期表示インデックスを指定可能
・カードはループ移動する
・一定時間ごとに自動スライド可能
・ホバー時は自動スライド停止
レスポンシブ要件:
・中央寄せレイアウト
・画面幅に応じて自然に縮小
・モバイルでもスワイプ操作可能
生成条件:
・純粋なHTML / CSS / JavaScriptのみ使用する
・外部フレームワークは使用しない
・CSS Transform(translateX / translateY / translateZ / rotate / scale)を使用する
・滑らかなアニメーションを実装する
デモ用カードデータとして以下の内容を使用する:
1. 静寂のラグジュアリー — 心地よい時間を生み出す上質な空間体験
2. 洗練されたライフスタイル — 日常をより美しく彩るデザイン思想
3. ダイナミックな躍動感 — 感性を刺激する力強い存在感
4. 細部へのこだわり — 丁寧に仕上げられた高品質なディテール
5. 新しい価値の創造 — 未来志向のアイデアが生む革新的体験
画像は実在するUnsplash画像URLを使用すること。