スクロールしているだけなのに、画面の空気が切り替わる。
次のセクションが「ぬっ」と前に出てきて、前のセクションをやんわり押しのける。
あの感じ。やりたくなりますよね。
ただし、油断するとすぐ事故ります。
重ねた瞬間にテキストが読めない。
SPでカクつく。
クリックできない。
そして最終的に「なんか戻して」と言われる。あるある。
この記事は、そういう悲しい歴史を繰り返さないための手引きです。
CSSだけで成立する基本形から、Intersection Observerで制御する実務形まで。
サンプルコード付きで、コピペして動くところまでまとめます。
そもそも なぜ重ねるのか
理由は単純で、「視線の誘導」が強いからです。
ページを下に読む体験は、放っておくと単調になります。
人はスクロールに慣れているので、見出しを見て、少し読んで、疲れたら離脱する。
そこへ重なり演出を入れると、場面転換に近い感触が出ます。
映画で言うところのカット割り。
舞台で言うところの暗転。
Webでやると、うまくハマった時だけ「次も見よう」が生まれる。
とはいえ、重ねること自体が目的になると終わりです。
演出は調味料。
塩を食べているのか肉を食べているのか分からなくなると、誰も幸せになりません。
やめたくなる瞬間 そしてやめるべきケース
まず、やめる必要がある場面を先に置きます。
作り始めると止まらないタイプの人ほど、ここが効きます。
1) コンテンツ量が多い記事やFAQ
読み物が長いほど、読者は「早く情報に着地したい」。
そこで画面が派手に切り替わると、テンポが崩れます。
特にFAQのように探し読みされるページでは、重なりはノイズになりやすい。
2) CTA直前の情報密度が高い
申し込み直前、問い合わせ直前。
人は慎重になっているので、派手な演出が不安を生みます。
「今クリックしていいやつ?」と迷わせた時点で負け。
3) 端末スペック差が大きいターゲット
BtoCで幅広い層を相手にするなら、SPの古い端末は現役です。
GPU負荷が高い演出は、体感で分かるレベルの引っかかりを起こします。
あなたのMacBookでは滑らかでも、相手の端末は別世界。ここ、現場の罠。
では、やる価値があるケースはどこか。
ブランド訴求ページ、採用LP、サービスの世界観を押すトップ。
この辺りは重なりが強い武器になります。
重なり演出の基本構造 まずはCSSだけで作る
最初に言っておきます。
「JSで頑張る」のは最後でいい。
まずはCSSだけで成立する形を作る。これが保守の勝ち筋です。
CSSだけでの基本は2つ。
stickyで「前のセクションを貼る」。
次のセクションをマイナスマージンなどで「前に押し出す」。
やってることは地味ですが、完成すると気持ちは派手。
メリット
- JS不要なので壊れにくい
- パフォーマンスが読みやすい
- 保守がラク。引き継ぎで揉めにくい
デメリット
- 切り替え瞬間の細かい制御は苦手
- デザインによってはマージン調整が面倒
- stickyの理解が浅いとハマる
サンプルコード1 CSSだけで作る重なりスクロール
この形がベースになります。
最小構成で、見た目の重なりが成立するパターン。
HTML
<section class="section section--sticky">
<div class="section__inner">
<h2>First Section</h2>
<p>ここが最初のコンテンツです。文章が入ります。</p>
</div>
</section>
<section class="section section--next">
<div class="section__inner">
<h2>Second Section</h2>
<p>次のコンテンツが上から重なってきます。</p>
</div>
</section>
<section class="section">
<div class="section__inner">
<h2>Third Section</h2>
<p>同じ要領で続けられます。やり過ぎは禁物。</p>
</div>
</section>
CSS
.section {
position: relative;
min-height: 100vh;
}
.section__inner {
padding: 80px 40px;
background: #fff;
}
/* 前のセクションを貼る */
.section--sticky {
position: sticky;
top: 0;
z-index: 1;
}
/* 次のセクションを押し上げて重なりに見せる */
.section--next {
margin-top: -40vh;
z-index: 2;
}
この時点で「重なって見える」は完成です。
でも、見た目だけで満足すると危ない。
次に待っているのは、クリック不能、スクロールの違和感、レイアウト崩れ。三点セット。
事故るポイント クリックできない 文字が読めない 問い合わせが減る
重なり演出は、z-indexとpointer-eventsと背景の3つでだいたい事故ります。
ここは笑えないので真顔でいきます。
z-indexの方針を決める
「どれが上か」を場当たりで決めると、後から修正不能になります。
おすすめは、セクション単位でレイヤーを付けること。
そしてセクション内の要素はなるべくz-indexを触らない。これが平和。
背景を必ず持たせる
重なりは透けると一気に読めなくなります。
白背景、薄いぼかし、疑似要素でのオーバーレイ。何でもいい。
「読める」を担保してから遊ぶ。順番が逆だと破綻する。
クリック領域とpointer-events
前のセクションが上に残っていると、見えていないのにクリックを奪うことがあります。
その時はpointer-events: noneを「残る側」に付けるのが定石。
ただし、残る側にリンクがあるなら、それはそれで困る。設計で解決する場面です。
切り替えの瞬間を制御したい時 CSSだけでは物足りない
切り替え演出に「合図」を入れたい。
テキストがふわっと出る。
写真がスッと寄る。
この手の演出が欲しくなる瞬間があります。
ここでscrollイベントを足すと、だいたい後悔します。
古い実装にありがちな「毎フレーム計算」は、2026年の空気には合わない。
ではどうするか。Intersection Observerです。
あなたは今、こう思ってませんか。
「Intersection Observerって、結局ハマるやつでしょ?」
分かります。私も最初にハマりました。だが、正しく使うと強い。
メリット
- スクロール連動の負荷が軽い
- しきい値で「合図」を作りやすい
- コードが短い。読みやすい
デメリット
- threshold設計を雑にすると期待とズレる
- アニメの作り込みは別途設計が要る
- SPのアドレスバー挙動など環境差は残る
サンプルコード2 Intersection Observerで重なり開始をトリガーする
スクロールの「ここから重ねる」を制御する実務形です。
CSSの基本構造はそのまま。
クラスを付け外しするだけ。賢い。
HTML
<section class="section section--sticky js-trigger">
<div class="section__inner">
<h2>Trigger Section</h2>
<p>このセクションが合図になります。ここが交差したら次を出す。</p>
</div>
</section>
<section class="section section--overlay js-target" aria-live="polite">
<div class="section__inner">
<h2>Overlay Section</h2>
<p>クラスが付いたら出現します。演出は控えめが勝ち。</p>
</div>
</section>
<section class="section">
<div class="section__inner">
<h2>Next Section</h2>
<p>この先は通常の流れに戻してもいいし、同じ構造を繰り返してもOK。</p>
</div>
</section>
CSS
.section {
position: relative;
min-height: 100vh;
}
.section__inner {
padding: 80px 40px;
background: #fff;
}
.section--sticky {
position: sticky;
top: 0;
z-index: 1;
}
/* ここが重なりの演出 */
.section--overlay {
margin-top: -40vh;
z-index: 2;
transform: translateY(80px);
opacity: 0;
transition: transform 600ms ease, opacity 600ms ease;
}
.section--overlay.is-active {
transform: translateY(0);
opacity: 1;
}
/* SPでは演出を弱める例 */
@media (max-width: 767px) {
.section__inner {
padding: 56px 20px;
}
.section--overlay {
margin-top: -24vh;
transform: translateY(40px);
transition: transform 420ms ease, opacity 420ms ease;
}
}
JavaScript
const trigger = document.querySelector(".js-trigger");
const target = document.querySelector(".js-target");
if (trigger && target) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
target.classList.add("is-active");
}
});
},
{
root: null,
threshold: 0.5,
}
);
observer.observe(trigger);
}
クラスが付くタイミングを一回にするなら、この形で十分。
行ったり来たりで状態を戻すなら、isIntersectingがfalseの時にremoveも書きます。
ただ、戻す演出は読者にとってうるさい時が多い。私は基本、出したら出しっぱなし派です。
実務での作り方 手順を短く でも抜けなく
ここからは進め方。
現場で早く作るには、順番が命です。
1) まず静的に重ねる
CSSだけで重なりが成立するか確認します。
ここで詰まったら、JSを入れても直りません。順番が逆。
2) 読めるかどうかを先に決める
文字が読める背景、行間、コントラスト。
演出より先にここを固めます。
「読めないけど雰囲気いい」は、だいたい炎上の予告編。
3) 切り替えの合図が必要ならObserverを足す
合図が要らないなら足しません。
JSは増えるほど説明コストが増えます。
あなたの未来の自分が困る。夜中に。
4) SPでの違和感チェック
SPで見て、微妙なら弱める。
弱めてまだ微妙なら切る。
ここで引ける人が、プロジェクトを救います。
ちょっと聞きたい。
あなたの案件、重なりを入れる場所は「本当にそこ」で合ってますか。
入れたいから入れる、になってない?
よくある質問 っぽい落とし穴
stickyが効かない
親要素にoverflow: hiddenやoverflow: autoが付いていると、stickyが無効化されることがあります。
また、transformが付いた親があると座標系の問題が出ることもある。
困ったら「stickyの親から上」を疑う。これが近道。
重なりがガタつく
画像の読み込みやフォントの遅延でレイアウトが動くと、重なりが揺れます。
画像はwidth/height指定、フォントはfont-displayやpreloadを検討。
地味ですが、体感に効きます。
次セクションのリンクが押せない
見えない要素が上に残ってクリックを奪うケース。
z-indexを整理するか、必要に応じてpointer-eventsを調整します。
ただし乱用すると別の地雷が生まれるので、設計で解くのが本筋。
メリットとデメリットを正面から
ここで一回、整理します。
推しポイントも、嫌いポイントも出します。偏見込み。
メリット
- スクロールに物語性が出る
- ファーストビュー以外でも視線を掴める
- ブランド訴求や採用で刺さりやすい
- 正しく作ればJSが少なく済む
デメリット
- やり過ぎると一気に胡散臭いサイトになる
- SPの実機で差が出やすい
- コンテンツの読みやすさを壊しやすい
- 調整が中途半端だと安っぽく見える
「安っぽい」は本当に怖い。
一回そう見えると、サイト全体の信用に波及します。
演出って、見た目だけの話じゃないんですよね。
読者に有益な追加テクニック もう一段だけ上げる
prefers-reduced-motionへの配慮
動きが苦手な人もいます。
OS設定でアニメを減らしている場合は、素直に弱める。
これ、やるとサイトの品が上がる。
@media (prefers-reduced-motion: reduce) {
.section--overlay {
transition: none;
transform: none;
opacity: 1;
}
}
「重なり」は1ページに多くても2回
私はこれを守る派です。
3回目から慣れます。慣れた演出はただの邪魔。
ここで止められるかどうかが、デザインと実装の差になります。
余白をケチらない
重なりは密度が上がります。
密度が上がると、疲れます。
だから余白を増やす。逆張りが正解の場面。
完全版 まとめて使える最小テンプレ
最後に、ページとしてまとめて動かせる最小テンプレを置きます。
このままHTMLファイルに貼れば動く。
案件ごとに背景や余白を調整して使ってください。
HTML
<main>
<section class="section section--sticky js-trigger">
<div class="section__inner">
<h2>First</h2>
<p>ここが最初のセクション。stickyで貼り付きます。</p>
<p>文章量はほどほどに。長いと演出が負けます。</p>
</div>
</section>
<section class="section section--overlay js-target">
<div class="section__inner">
<h2>Second</h2>
<p>スクロールの合図で、ふわっと前に出ます。</p>
<p>ここに画像やCTAを置くと「次へ」が生まれやすい。</p>
</div>
</section>
<section class="section">
<div class="section__inner">
<h2>Third</h2>
<p>通常のセクション。ここからは落ち着かせても良い。</p>
</div>
</section>
</main>
<script>
const trigger = document.querySelector(".js-trigger");
const target = document.querySelector(".js-target");
if (trigger && target) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
target.classList.add("is-active");
}
});
},
{ threshold: 0.5 }
);
observer.observe(trigger);
}
</script>
CSS
main {
display: block;
}
.section {
position: relative;
min-height: 100vh;
}
.section__inner {
padding: 80px 40px;
background: #fff;
}
.section--sticky {
position: sticky;
top: 0;
z-index: 1;
}
.section--overlay {
margin-top: -40vh;
z-index: 2;
transform: translateY(80px);
opacity: 0;
transition: transform 600ms ease, opacity 600ms ease;
}
.section--overlay.is-active {
transform: translateY(0);
opacity: 1;
}
@media (max-width: 767px) {
.section__inner {
padding: 56px 20px;
}
.section--overlay {
margin-top: -24vh;
transform: translateY(40px);
transition: transform 420ms ease, opacity 420ms ease;
}
}
@media (prefers-reduced-motion: reduce) {
.section--overlay {
transition: none;
transform: none;
opacity: 1;
}
}
ここまで読んで、まだ「もっと派手にしたい」と思ってますか。
それなら、まずページ内で一番伝えたい情報を1つ選ぶ。
重なりはその1つのために使う。私はそういう使い方が好きです。
重なりは、上手く決まると気持ちいい。
決まらないと、ただの自己紹介ミス。
どっちに転ぶかは設計で決まります。ここ、割と本気で。

