Sticky スタックスクロール
position: sticky と top のずらしで、スクロールするとカードが重なり積み上がるストーリーテリング向けレイアウト。進捗バーをJSで連動。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura ライブツアー物語:sticky スタックでスクロール演出 -->
<div class="sk-ss">
<p class="sk-ss__hint">↓ スクロールでツアーの軌跡をたどる</p>
<div class="sk-ss__scroll" id="skSsScroll">
<section class="sk-scard sk-scard--1">
<span class="sk-scard__no">DAY 01</span>
<div class="sk-scard__thumb" style="background-image:url('https://picsum.photos/300/200?random=71')"></div>
<div class="sk-scard__body">
<h2>東京 開幕</h2>
<p>満開の桜とともにツアー「春一会」開幕。5人の笑顔がアリーナを包みました。</p>
</div>
</section>
<section class="sk-scard sk-scard--2">
<span class="sk-scard__no">DAY 02</span>
<div class="sk-scard__thumb" style="background-image:url('https://picsum.photos/300/200?random=72')"></div>
<div class="sk-scard__body">
<h2>大阪 城ホール</h2>
<p>新曲「ペタル・ダンス」を初披露。ピンクのペンライトが客席を染めました。</p>
</div>
</section>
<section class="sk-scard sk-scard--3">
<span class="sk-scard__no">DAY 03</span>
<div class="sk-scard__thumb" style="background-image:url('https://picsum.photos/300/200?random=73')"></div>
<div class="sk-scard__body">
<h2>名古屋 夜公演</h2>
<p>ことねのソロパートに大歓声。アンコールは全員で「桜ロード」を熱唱。</p>
</div>
</section>
<section class="sk-scard sk-scard--4">
<span class="sk-scard__no">FINAL</span>
<div class="sk-scard__thumb" style="background-image:url('https://picsum.photos/300/200?random=74')"></div>
<div class="sk-scard__body">
<h2>福岡 ファイナル</h2>
<p>「また来年、満開の季節に。」次のツアー開催も電撃発表されました。</p>
</div>
</section>
</div>
<div class="sk-ss__progress" aria-hidden="true"><i id="skSsBar"></i></div>
</div>
CSS
/* Sakura:position: sticky でライブツアーのストーリーカードを積み上げる */
:root {
--pink: #ffd1e0;
--pink-deep: #ff8fb3;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
background: linear-gradient(165deg, #fff0f6, #ffe3ee);
color: #6a2740;
height: 400px;
overflow: hidden;
}
.sk-ss { position: relative; height: 400px; display: flex; flex-direction: column; }
.sk-ss__hint {
position: absolute;
top: 8px;
left: 50%;
transform: translateX(-50%);
z-index: 5;
font-size: 11px;
color: #b05a7e;
pointer-events: none;
background: rgba(255, 255, 255, 0.8);
padding: 4px 12px;
border-radius: 999px;
box-shadow: 0 2px 8px rgba(214, 94, 140, 0.18);
}
/* スクロール領域 */
.sk-ss__scroll {
flex: 1;
overflow-y: auto;
scroll-behavior: smooth;
padding: 40px 16px 260px; /* 末尾余白でスタックを成立させる */
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.sk-ss__scroll::-webkit-scrollbar { width: 7px; }
.sk-ss__scroll::-webkit-scrollbar-thumb { background: var(--pink-deep); border-radius: 8px; }
/* スタックカード:sticky で上端に貼り付き、次が覆い被さる */
.sk-scard {
position: sticky;
top: 20px;
width: min(440px, 100%);
min-height: 130px;
border-radius: 18px;
padding: 14px;
display: flex;
gap: 14px;
align-items: center;
box-shadow: 0 18px 36px -18px rgba(214, 94, 140, 0.6);
background: #fff;
border: 1px solid #ffd9e6;
}
.sk-scard__no {
position: absolute;
top: 12px;
right: 14px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.18em;
color: var(--pink-deep);
}
.sk-scard__thumb {
flex: 0 0 110px;
height: 100px;
border-radius: 12px;
background-size: cover;
background-position: center;
}
.sk-scard__body { flex: 1; }
.sk-scard h2 { font-size: 18px; font-weight: 800; margin-bottom: 6px; }
.sk-scard p { font-size: 12px; line-height: 1.7; color: #8a4a64; }
/* top をずらして重なりの段差を出す+各カードに桜色のアクセント */
.sk-scard--1 { top: 20px; border-top: 4px solid #ffb3cd; }
.sk-scard--2 { top: 32px; border-top: 4px solid #ff9cc0; }
.sk-scard--3 { top: 44px; border-top: 4px solid #ff85b3; }
.sk-scard--4 { top: 56px; border-top: 4px solid #ff6fa6; }
/* スクロール進捗バー */
.sk-ss__progress {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 5px;
background: #ffe0ea;
}
.sk-ss__progress i {
display: block;
height: 100%;
width: 0%;
background: linear-gradient(90deg, #ffb3cd, #ff6fa6);
transition: width 0.1s linear;
}
@media (prefers-reduced-motion: reduce) {
.sk-ss__scroll { scroll-behavior: auto; }
.sk-ss__progress i { transition: none; }
}
JavaScript
// スクロール量に応じてツアー進捗バーを更新(null安全・受動リスナ)
(() => {
const scroller = document.getElementById("skSsScroll");
const bar = document.getElementById("skSsBar");
if (!scroller || !bar) return;
const update = () => {
const max = scroller.scrollHeight - scroller.clientHeight;
const ratio = max > 0 ? scroller.scrollTop / max : 0;
bar.style.width = Math.min(100, ratio * 100).toFixed(1) + "%";
};
scroller.addEventListener("scroll", update, { passive: true });
update(); // 初期表示
})();
コード
HTML
<!-- position: sticky スタック:スクロールでカードが重なり積み上がるレイアウト -->
<div class="ss">
<p class="ss__hint">↓ スクロールしてカードを重ねる</p>
<div class="ss__scroll" id="ssScroll">
<section class="scard scard--1">
<span class="scard__no">01</span>
<h2>Sticky で重ねる</h2>
<p>各カードに top を指定すると、上端で止まり次が覆い被さります。</p>
</section>
<section class="scard scard--2">
<span class="scard__no">02</span>
<h2>奥行きの演出</h2>
<p>少しずつ縮小・回転させると立体的なスタックに見えます。</p>
</section>
<section class="scard scard--3">
<span class="scard__no">03</span>
<h2>JS不要が基本</h2>
<p>進捗バーだけ JS で連動。レイアウト自体は CSS sticky のみ。</p>
</section>
<section class="scard scard--4">
<span class="scard__no">04</span>
<h2>最後のカード</h2>
<p>ストーリーテリングや LP のヒーロー連結に最適です。</p>
</section>
</div>
<div class="ss__progress" aria-hidden="true"><i id="ssBar"></i></div>
</div>
CSS
/* 全体:暗い背景にカラフルなスタックカード */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Hiragino Sans", system-ui, sans-serif;
background: #0d1018; color: #f8fafc; height: 100vh; overflow: hidden;
}
.ss { position: relative; height: 100vh; display: flex; flex-direction: column; }
.ss__hint {
position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
z-index: 5; font-size: 12px; color: #94a3b8; pointer-events: none;
background: rgba(13,16,24,.7); padding: 4px 12px; border-radius: 20px;
}
/* スクロール領域 */
.ss__scroll {
flex: 1; overflow-y: auto; scroll-behavior: smooth;
padding: 48px 16px 60vh; /* 末尾に余白を作りスタックを成立させる */
display: flex; flex-direction: column; gap: 26px; align-items: center;
}
.ss__scroll::-webkit-scrollbar { width: 8px; }
.ss__scroll::-webkit-scrollbar-thumb { background: #334155; border-radius: 8px; }
/* スタックカード:sticky で上端に貼り付く */
.scard {
position: sticky; top: 24px;
width: min(560px, 100%); min-height: 180px;
border-radius: 18px; padding: 22px 24px;
display: flex; flex-direction: column; gap: 8px; justify-content: center;
box-shadow: 0 24px 50px -24px rgba(0,0,0,.7);
color: #0b1020;
}
.scard__no { font-size: 12px; font-weight: 800; letter-spacing: .2em; opacity: .65; }
.scard h2 { font-size: clamp(20px, 4vw, 28px); font-weight: 800; }
.scard p { font-size: 14px; line-height: 1.7; max-width: 44ch; opacity: .9; }
/* 重なり時の段差を出すため top をずらす */
.scard--1 { top: 24px; background: linear-gradient(135deg, #fda4af, #fb7185); }
.scard--2 { top: 38px; background: linear-gradient(135deg, #a5b4fc, #818cf8); color: #f8fafc; }
.scard--3 { top: 52px; background: linear-gradient(135deg, #6ee7b7, #34d399); }
.scard--4 { top: 66px; background: linear-gradient(135deg, #fcd34d, #fbbf24); }
/* スクロール進捗バー */
.ss__progress {
position: absolute; left: 0; right: 0; bottom: 0; height: 5px;
background: #1e293b;
}
.ss__progress i {
display: block; height: 100%; width: 0%;
background: linear-gradient(90deg, #fb7185, #818cf8, #34d399);
transition: width .1s linear;
}
@media (prefers-reduced-motion: reduce) {
.ss__scroll { scroll-behavior: auto; }
.ss__progress i { transition: none; }
}
JavaScript
// スクロール量に応じて進捗バーを更新(null安全・受動リスナで滑らかに)
(() => {
const scroller = document.getElementById('ssScroll');
const bar = document.getElementById('ssBar');
if (!scroller || !bar) return;
const update = () => {
const max = scroller.scrollHeight - scroller.clientHeight;
const ratio = max > 0 ? scroller.scrollTop / max : 0;
bar.style.width = Math.min(100, ratio * 100).toFixed(1) + '%';
};
scroller.addEventListener('scroll', update, { passive: true });
update(); // 初期表示
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「Sticky スタックスクロール」の効果を追加してください。
# 追加してほしい効果
Sticky スタックスクロール(レイアウト & グリッド)
position: sticky と top のずらしで、スクロールするとカードが重なり積み上がるストーリーテリング向けレイアウト。進捗バーをJSで連動。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- position: sticky スタック:スクロールでカードが重なり積み上がるレイアウト -->
<div class="ss">
<p class="ss__hint">↓ スクロールしてカードを重ねる</p>
<div class="ss__scroll" id="ssScroll">
<section class="scard scard--1">
<span class="scard__no">01</span>
<h2>Sticky で重ねる</h2>
<p>各カードに top を指定すると、上端で止まり次が覆い被さります。</p>
</section>
<section class="scard scard--2">
<span class="scard__no">02</span>
<h2>奥行きの演出</h2>
<p>少しずつ縮小・回転させると立体的なスタックに見えます。</p>
</section>
<section class="scard scard--3">
<span class="scard__no">03</span>
<h2>JS不要が基本</h2>
<p>進捗バーだけ JS で連動。レイアウト自体は CSS sticky のみ。</p>
</section>
<section class="scard scard--4">
<span class="scard__no">04</span>
<h2>最後のカード</h2>
<p>ストーリーテリングや LP のヒーロー連結に最適です。</p>
</section>
</div>
<div class="ss__progress" aria-hidden="true"><i id="ssBar"></i></div>
</div>
【CSS】
/* 全体:暗い背景にカラフルなスタックカード */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Hiragino Sans", system-ui, sans-serif;
background: #0d1018; color: #f8fafc; height: 100vh; overflow: hidden;
}
.ss { position: relative; height: 100vh; display: flex; flex-direction: column; }
.ss__hint {
position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
z-index: 5; font-size: 12px; color: #94a3b8; pointer-events: none;
background: rgba(13,16,24,.7); padding: 4px 12px; border-radius: 20px;
}
/* スクロール領域 */
.ss__scroll {
flex: 1; overflow-y: auto; scroll-behavior: smooth;
padding: 48px 16px 60vh; /* 末尾に余白を作りスタックを成立させる */
display: flex; flex-direction: column; gap: 26px; align-items: center;
}
.ss__scroll::-webkit-scrollbar { width: 8px; }
.ss__scroll::-webkit-scrollbar-thumb { background: #334155; border-radius: 8px; }
/* スタックカード:sticky で上端に貼り付く */
.scard {
position: sticky; top: 24px;
width: min(560px, 100%); min-height: 180px;
border-radius: 18px; padding: 22px 24px;
display: flex; flex-direction: column; gap: 8px; justify-content: center;
box-shadow: 0 24px 50px -24px rgba(0,0,0,.7);
color: #0b1020;
}
.scard__no { font-size: 12px; font-weight: 800; letter-spacing: .2em; opacity: .65; }
.scard h2 { font-size: clamp(20px, 4vw, 28px); font-weight: 800; }
.scard p { font-size: 14px; line-height: 1.7; max-width: 44ch; opacity: .9; }
/* 重なり時の段差を出すため top をずらす */
.scard--1 { top: 24px; background: linear-gradient(135deg, #fda4af, #fb7185); }
.scard--2 { top: 38px; background: linear-gradient(135deg, #a5b4fc, #818cf8); color: #f8fafc; }
.scard--3 { top: 52px; background: linear-gradient(135deg, #6ee7b7, #34d399); }
.scard--4 { top: 66px; background: linear-gradient(135deg, #fcd34d, #fbbf24); }
/* スクロール進捗バー */
.ss__progress {
position: absolute; left: 0; right: 0; bottom: 0; height: 5px;
background: #1e293b;
}
.ss__progress i {
display: block; height: 100%; width: 0%;
background: linear-gradient(90deg, #fb7185, #818cf8, #34d399);
transition: width .1s linear;
}
@media (prefers-reduced-motion: reduce) {
.ss__scroll { scroll-behavior: auto; }
.ss__progress i { transition: none; }
}
【JavaScript】
// スクロール量に応じて進捗バーを更新(null安全・受動リスナで滑らかに)
(() => {
const scroller = document.getElementById('ssScroll');
const bar = document.getElementById('ssBar');
if (!scroller || !bar) return;
const update = () => {
const max = scroller.scrollHeight - scroller.clientHeight;
const ratio = max > 0 ? scroller.scrollTop / max : 0;
bar.style.width = Math.min(100, ratio * 100).toFixed(1) + '%';
};
scroller.addEventListener('scroll', update, { passive: true });
update(); // 初期表示
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。