BeAct Co., Ltd.

BLOG
社員ブログ

CSSの重ね順とz-indexを「レイヤー」で考える・4

こんにちは!今日もプログラミングを楽しんでいますか?

これまでの3回にわたり、CSSの「重ね合わせコンテキスト」の仕組みと、それがどのように「レイヤー」として機能するかを解説してきました。

最終回となる今回は、これまでの知識を総動員して、重ね順を『設計』し、コントロールする方法を解説します。もう「とりあえず z-index: 9999;を打つ」必要はありません。


今回作るWebサイトの要件

以下の要素を持つ、標準的なモダンサイトを想定します。

  • ヘッダー: 画面上部に常に固定。
  • メインコンテンツ: 内容に応じてスクロール。
  • ハンバーガーメニュー: 画面左からスライドして出現する。出現時は背景をぼかす。
  • モーダル(<dialog>): ボタンクリックで中央に出現。背景をぼかす。

まずは「レイヤー構造」を可視化する

いきなりCSSを書き始めるのは、設計図なしでビルを建てるようなものです。まずは今回作るサイトの構成を、横から見た「層(レイヤー)」として整理します。これができていれば、実装で迷うことはありません。

こんかいは、このように整理してみました。

  • Layer 0 (底面): 本文(背景、通常のテキスト、画像)。
  • Layer 1 (固定要素): ヘッダー (position: fixed)。
  • Layer 2 (オーバーレイ): ハンバーガーメニュー (position: fixed)。
  • 最上位層: <dialog> 要素。

ここで重要なのは、「何が、何を追い越すべきか」という優先順位の確定です。

ヘッダーと本文の「力関係」を固定する

まず、ヘッダーを position: fixed; で固定します。

header {
  position: fixed;
  top: 0;
  background: rgba(255, 255, 255, 0.8);
}

main {
  /* いろんな設定 */
}

ここで「メインコンテンツ内の画像に transform をかけたら、ヘッダーを突き抜けて上にきてしまった!」……というトラブルは「あるある」のひとつです。

header {
  position: fixed;
  top: 0;
  background: rgba(255, 255, 255, 0.8);
}

main {
  /* いろんな設定 */
}

main img {
  transform: translateX(10px);
}
ヘッダーの下にあってほしいのに…
ヘッダーの上に来てしまった!

これを z-index: 10000; を使わずに解決するには、どうすればいいのでしょうか?

答え

突き抜けたのは、transform によって、メインコンテンツ側に新しい「重ね合わせコンテキスト」が発生したからです。重ね合わせコンテキストは、きょうだいレイヤー同士であればあとから登場したほうが手前に来ます。レイヤー設計していない失敗例の典型です。なので、最初に考えた設計を、実装に反映させましょう

header {
  position: fixed;
  /* ヘッダーの強さは 1 でしたね */
  z-index: 1;
  top: 0;
  background: rgba(255, 255, 255, 0.8);
}

main {
  position: relative; /* デフォルト(static)だと、z-index の対象にできません! */
  /* 本文の強さは 0 でしたね */
  z-index: 0;
  /* これで main は、晴れて「レイヤーコンテナ」となりました! */
}

main img {
  /* img は、「main レイヤーコンテナ」の子要素となりました!
     もう、header の「きょうだいレイヤー」にはなりません! */
  transform: translateX(10px);
}

すると、結果は……。

これが「設計」の力です!

ぶじ、期待通りの動作になりました。headermain がきょうだいレイヤーとなった以上、もう main の中で重ね合わせコンテキストがどんなに暴れまわっても、header の上に来ることは決してありません! 設計の重要さが、わかってきたでしょうか?

ハンバーガーメニューと「ボカし」の設計

次に、画面を覆うハンバーガーメニューです。ここで「背景をボカしたい(backdrop-filter)」という要望に応えてみましょう。

よくある失敗は、メニューの背後に「黒い半透明の板(オーバーレイ)」を、メニューの子要素として配置してしまうこと。これでは、ボカしたい対象(メインコンテンツ)のきょうだいレイヤーではなくなるため、うまくボカせないことがあります。

実戦的な設計: メニュー自体を z-index: 2; とし、「その要素の疑似要素」で背景を覆います。

nav {
  position: fixed;
  inset: 0;
  /* ハンバーガーメニューの強さは 2 でしたね */
  z-index: 2;
}

/* メニューの背景としてのオーバーレイ */
nav::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(8px); /* 確実に背後をボカす */
  z-index: -1; /* メニュー内の各リンクより下に配置 */
}
どれが「きょうだいレイヤー」かを管理できていれば、簡単!

はい、ご覧の通りです。z-index: 2 という小さな数字で、ヘッダー(1)もメイン(0)もすべてを安全に覆い隠せました。

最強のカード <dialog> を切る

最後に、モーダルウインドウです。 これまでの CSS の常識では、モーダルは「HTML 構造の最後に置く」のが鉄則でした。そうしないと、どこかの親要素の overflow: hidden;z-index に妨げられるという、デバッグの困難な不具合を引き起こしてしまっていたからです。

しかし、現代に生きる私たちに、そんな必要はありません。そう、 <dialog> が生み出す最上位層が、多くのケースで解決してくれるからです!

// JSで呼び出す
const modal = document.querySelector('#my-modal');
modal.showModal(); // これが魔法の呪文

<dialog> については第2回で説明していますので、そちらを参照してください

まとめ:z-indexのインフレを止めるのは「あなたの設計」

全4回の連載を通じてお伝えしたかったのは、「z-indexは、場当たり的な修正のためのツールではない」ということです。

  • 「きょうだいレイヤー」と「レイヤーコンテナ」を意識して、親の階層を「設計」する
  • 大きな数字ではなく、意味のある小さな数字(0, 1, 2…)で管理する
  • ブラウザの標準機能(<dialog>)のお世話になる

この3点を守るだけで、CSS における「重ね順」のメンテナンス性は劇的に向上します!

最後に、今までの集大成のコード例をお見せします。

お疲れさまでした。それでは、よきコーディングライフを!