💤

【CSS】z-index管理の3指針

2024/07/03に公開

要素の重なりを制御する時に使うCSSと言えばz-indexですが、単純にその値が上下関係になるとは限らない、少しクセのあるプロパティです。Webサイト上の重なりにはスタッキングコンテキストと呼ばれる仕組みがあり、その関係性によってz-indexの値が1でも9999の上にくることがあり得ます。

z-indexとスタッキングコンテキストの関係については次の記事にて解説されています。

https://ics.media/entry/200609/

この記事では自分の考えたz-index管理についての指針を紹介します。

グローバルな重なりとローカルな重なり

Webサイトでの重なり表現には大きく分けて2つあります。ページ全体で重なりを管理したいものと、一部の領域で重なりを管理したいものです。この記事では前者をグローバルな重なり、後者をローカルな重なりと呼ぶことにします。

グローバルな重なりに含まれるのは、追従ヘッダーやモーダル、ハンバーガーメニュー、トースト通知などです。多くの場合ページコンテンツ領域よりも優先して上(あるいは下)に表示したい要素です。

ローカルな重なりは、アバターの上に表示される通知アイコンや写真に重なる文字といったような特定の要素同士における上下関係です。あくまで特定要素同士のみに着目しているので、それ以外の要素と重なりは基本的に関係ありません。

これらの性質を元にz-index管理のための指針を立てます。

指針1:ローカルな重なりはz-index:0で閉じ込める

上下の重なりの失敗の1つに、本来ローカルな重なりである要素がグローバルな重なりより上に来てしまうことがあります。これはグローバルな重なりと同一スタッキングコンテキスト上にあるのが原因です。ローカルな重なりはグローバルな重なりのスタッキングコンテキスト上にある必要はありません。

そこでローカルな重なりの基準となる要素のposition: relativez-index: 0つけることで新たなスタッキングコンテキストを発生させます。これにより、ローカルな重なりはこのスタッキングコンテキスト内に閉じ込められるのでグローバルの重なりへの影響が出ないようになります。閉じたスタッキングコンテキスト内では、たとえz-index: 9999にしても重なり関係はその外へ影響を及ぼすことはありません。

指針2:グローバルな重なりはbodyタグ直下、あるいは近いところに配置する

グローバルな重なりを表現したい部分なのに、思うように上に来ないといったことのあります。これは指針1とは逆に不適切なスタッキングコンテキストに閉じ込められているためです。グローバルな重なりを行うにはグローバルなスタッキングコンテキストにいる必要があります。階層深くにあると親要素の都合で不意にローカルなスタッキングコンテキストに閉じ込められる恐れがあります。

そうならないようにグローバルな重なりにしたい要素は<body>タグ直下のタグにするか、あるいはできるだけ近い階層に置くようにします。ReactやVue.jsを利用している場合はPortalと呼ばれるような機能を使うと簡単に実現できます。

モーダルやハンバーガーボタンなどの要素はbodyタグに近い部分に置くとよいでしょう。特にハンバーガーボタンはヘッダーの要素内に設置すると、ヘッダーのコンテキストに閉じ込められることがあります(例:固定ヘッダー)。この時、ヘッダー以上ハンバーガーボタン未満みたいなレイヤーを実現しようにもスタッキングコンテキストの関係で困難になります。ハンバーガーボタンはヘッダー要素から独立して配置するのも手です。

指針3:z-indexの値は変数を用いる

指針1と指針2は主にスタッキングコンテキストのコントロールに主眼を置いていましたが、この指針は同一スタッキングコンテキストにおける管理について着目しています。

同一スタッキングコンテキストにおける重なりの上下はz-indexの値の大小により決まります。この値を変数で管理します。変数で管理することの以下の利点があります。

  • 値に意味を与えられる(セマンティック化)
  • 値を集中管理できる
  • calc()を使った柔軟な対応ができる

それぞれについて説明します。

値に意味を与えられる

たとえばz-indexの値が2だったとしても、z-index: 1よりも上、3より下であること以上の意味は持ち合わせていません。さらに同一スタッキングコンテキストで同じ値がある場合はHTMLの出現順に応じて上下関係が決まるため、値そのものには要素間の重なり関係を決める意味を厳密には持っていません。

そこで、--header-z-index: 100のようにz-indexの値を変数化することで値に意味を与えられます。z-indexの値が意味を持ちセマンティックになることでコード上でも位置関係が直感的になります。

z-index: var(--header-z-index); 

コードを見ればこのz-indexの値がヘッダーの高さであることが明確になります。

値を集中管理できる

変数化することで各所のz-indexの値を1ヶ所で管理できます。

:root {
  --header-z-index: 100;
  --navigation-z-index: 200;
  --modal-z-index: 500;
  --local-z-index: 0;
}

特にグローバルな重なりの値を一ヶ所で管理すると上下関係が一目瞭然です。値の変更で重なり関係の不意な誤りにも気づきやすいですし、色んなファイル行ったり来たりせずに済みます。

calc()関数を使って計算ができる

ローカルな重なりは変数とcalc()関数を使って分かりやすく管理できるようになります。具体的には下記の通りです。(local-z-indexの値は0

.wrapper {
  position: relative;
  z-index: var(--local-z-index);
}

.item1 {
  position: absolute;
  z-index: calc(var(local-z-index) + 1);
}

.item2 {
  position: absolute;
  z-index: calc(var(local-z-index) + 2);
}

calc()関数と変数を使うメリットは、それがローカルに閉じたスタッキングコンテキストであるのが明確なこと、足し算で上下関係を設定できる点です。

ローカルなスタッキングコンテキストはグローバルなものに比べて細かい重なり関係になりがちです。その都度、z-indexの変数を用意するのは面倒ですし、変数名の重複も出るかもしれません。影響をローカルに閉じつつ管理できます。

まとめ

z-indexの仕様は複雑で、いつのまにか崩れていることもあります。今回紹介した3指針を用いることで完全に解決できるものではありませんが、z-indexの破綻を抑止できるはずです。z-indexと仲良くなって重なり関係をうまくコントロールしていきましょう。

Discussion