好きな食べ物15年間不動の一位唐揚げを、タコスが猛烈な勢いで追い越そうとしているフロントエンドエンジニアのyoseiです。
CSSをスタイルを組んでいくと、どこかで必ずお世話になるz-index。
z-indexとは要素同士の重なりを制御するプロパティなのですが、
他のプロパティに比べて、一発で書けない時あるなあ。
というのが近々の悩みでした。
「こんな重なりを目指したい」となった時に、要素の階層が複雑だったりすると、あれ??効いてない??なんてことがたまーにあるわけです。
でもこの時、いろんなパターンを試して「とりあえず目的通りにはなったわ!」というのはなんだか気持ちが悪いし、根本的な解決になっていない。。。
そこで今回は、一発でz-indexを書けるようになる事を目標に、自分がz-indexの何を理解できていないのか整理したものを記事にしてみたいと思います。
先日公開した、あんどぷらすインターンシップ2023冬にてこんな実装をしました。
一番手前に、「タイトル」があって、その次にメインの「コンテンツボックス」があって、その後ろに「ヒヨコチャン」が隠れている。
これが目指すべき重なり順です。
しかし僕は、途中こんなコードを書いてヒヨコちゃんが背景である.backgroundの後ろまで潜ってしまったことに首を傾げていました。
(説明のため、もとのコードを簡略化しています。)
See the Pen
Untitled by yosei (@yosei_1127)
on CodePen.
HTMLはシンプルです。.miniArticleの中に「タイトル」、「コンテンツボックス」、「ヒヨコチャン」と言う三つの要素がある。
通常はこの記述順のまま表示されるので、「ヒヨコチャン」が一番手前に出てきてしまいます。
なので、重なり順を操作するために、それぞれに対して順に「2」、「1」、「-1」と言う値のz-indexを与えました。
z-indexは正の数も負の数も与えることができるので、後ろに回り込ませると言うイメージからヒヨコチャンには負の数を与えました。
重なり順として一見正しそうな数字が振られているように感じます。
ですが、その結果ヒヨコちゃんは「タイトル」と「コンテンツボックス」の後ろに回り込んだはいいものの、さらに親であるbackgroundの裏にまで回り込んでしまっています。
前提として「z-indexは同一階層の要素にしか効かない」と言う認識を持っていた僕は、ここで混乱しました。
階層突き抜けてね??
と。
本当に「z-indexは同一階層の要素にしか効かない」のであれば、親である.miniArticleの裏に回り込んでしまうと言うことはないはずなのですが。。。
結論から言うともちろんこれらは、CSSの仕様に従った正しい挙動です。
つまり、間違っていたのは「z-indexは同一階層にしか効かない」という僕の認識の方でした!アレレ~
実際この記事を書くにあたって色々調べていく中で、z-indexは「同一階層にしか効かない」と言い切っている情報もちらほら見かけましたが、これは実は間違いでした。
今確認したように、子が親の後ろに潜り込んでいる訳なので、同一階層じゃなくてもz-indexが効くケースはあります。
じゃあ、z-indexはHTMLの階層に関係なくドキュメント全体を見ながら数字を振っていかなければいけないかと言うとそれも違います。
もしそうだったら、かなり数字の管理が大変になりそうです。
結論としてz-indexが効く範囲は、きちんと制御することができるものなのです。
そしてこの「z-indexが効く範囲」のことをスタッキングコンテキスト(スタッキング文脈)と言います。
このスタッキングコンテキストには生成される条件があるので、場合によってz-indexの効く範囲が変化して分かりづらくなっていると言うのが、つまづきポイントです。
このスタッキングコンテキストを踏まえながら先ほど何が起きていたか整理しましょう。
前提として、z-indexと言うプロパティは、特に明記しないときはz-index: auto;が自動で当たっており、これは見え方としてはz-index: 0;を指定した時と同じ扱いです。
また、もう一つの前提として、z-indexの値が同じもの同士は、HTMLに記述順に従って重なります。
なので、さっきのケースで仮にz-indexを何も指定しなかった場合、
HTML要素はHTMLの記述順に従って、z-index: 0;と言う地平に、背景も他の要素も全部まとめてグシャッと重なっている状態になります。
これは、サイトの要素同士の重なりを横から覗いた時のイメージ図です。(ユーザーが見ている方向に注意してください。)
次に、先程のようにそれぞれの要素にz-index「2」「1」「-1」が与えられているとこうなります。
要素は、スタッキングコンテキストの中で、z-indexに従って並んでいるので、デフォルトでz-index: auto;が当たっている背景よりも裏にヒヨコチャンが回り込んでいるのが分かります。
これが、ヒヨコチャンが背景の裏に回り込んでしまった理由です。
z-indexで重なりが指定されている分、z-index: 0;の地表からばらけた感じがありますね。
ただ、説明をすっ飛ばしましたが、図にはスタッキングコンテキスト(z-indexが効く範囲)が既に存在しています。しれっとたたずんでいますね。なんでここにいるんだあ、おめえはよお。
突然出てきた理由、それはスタッキングコンテキストの重要な生成条件、
bodyは特に指定がなくてもスタッキングコンテキストを生成する
にあります。
はい、つまり特に何もしなくてもbodyは常にスタッキングコンテキストを生成しています。
z-indexの数字の大小はこのスタッキングコンテキストの範囲で管理されます。
ただ、ちょっと待って。
これこそ「z-indexの数字をドキュメント全体で管理している」状態なのでは??
つまり、このままでは、z-indexが増えていった時、その数字同士をとても管理しにくい状態。
でもこの問題は、スタッキングコンテキストの中に新たなスタッキングコンテキストを生成することで解決することが出来ます。
bodyは自動でスタッキングコンテキストを生成しているという話をしましたが、
任意でスタッキングコンテキストが生成するための条件がちゃんとあるのです。
その代表的なひとつが
positionの値がabsoluteあるいはrelative、かつz-indexの値がauto以外
と言うもの。
例えば、posititon: relative; zi-index: 0;が例です。
え?これは何が起こるの?と思うような一見意味のない記述なのですが、これによって新たなスタッキングコンテキストが生成されるのです。
See the Pen
Untitled by yosei (@yosei_1127)
on CodePen.
今回のケースでは、「ヒヨコチャン」達の親である.miniArticleに、position: relative; z-index: 0;を指定し、スタッキングコンテキストを新たに生成しました。
それによって、元からあったbodyのスタッキングコンテキストの中に新たなスタッキングコンテキスト生成され、スタッキングコンテキストは 入れ子の状態になります。
そして、ここで重要なのはスタッキングコンテキスト内の要素は、どんなz-indexの値が与えられても、スタッキングコンテキストの外には出ないと言うルールがあることです。
今回.miniArticleに新たにスタッキングコンテキストが生成されたので、たとえ、子の「ヒヨコチャン」にz-index: -9999;という値が与えられても、.miniArticleと言うスタッキングコンテキストの外に出ることはありません。
これによって、z-indexの効く範囲が制限され、ヒヨコチャンが背景の後ろに回り込むこともなくなります。
z-indexを管理したい範囲と言うのが明確にあれば、このスタッキンキングコンテキストの入れ子生成によって、z-indexをより適切な範囲で管理することが出来るようになる訳です。
その他の主なスタッキングコンテキスト生成条件です。
・ position の値が fixed あるいは sticky
・ display: flexあるいはdisplay: gridの子要素で、かつz-indexの値がauto以外
・ opacityの値が1以外
・ transform、filter、perspective、clip-path、mask、mask-image、mask-borderの値がnone以外
え、この時も生成されてたんだと言う感じですね。意外に多いです。
もしかしたら、今までz-indexの適応範囲が場合によって違くて混乱してたのも、これらの条件を偶然満たして、スタッキングコンテキストが意図せず生成されていたから、というケースも全然ありそうです。
僕が、z-indexで混乱したポイント、ふたつ目はもっとよくあるケースです。
まず、先程の話にしたがって.miniArticleで適切にスタッキングコンテキストを生成した上で「タイトル」「コンテンツボックス」「ヒヨコチャン」をz-indexで並び替えるべく、それぞれに「3」「2」「1」と言うz-indexを与えます。よ〜〜し今度は完璧完璧。
See the Pen
Untitled by yosei (@yosei_1127)
on CodePen.
huh?????
今度は、ヒヨコチャンが中途半端に前に出てきたぞ。
はい、これは凡ミス系ですが、結構やります。
原因は、
z-indexはposition:relative か absolute か fixed を併記しないと使えない
ことにあります。
今回のケース、「タイトル」と「ヒヨコチャン」にはz-indexと一緒にたまたまposition: abosolute;が描かれていたので、ちゃんとz-indexが効いていました。
でも、「コンテンツボックス」はz-indexの表記のみ。
つまり、z-index: 2;は効いておらず、デフォルトのz-inde: auto;と同じ扱いになって、z-index: 1である「ヒヨコチャン」、それよりも前に出てきてしまった訳です。
これも、一見「コンテンツボックス」にはposition: relative;という記述を書く理由がないように思えますが、z-indexを使うためにあえて書く必要があるということですね。
こう見ると、z-indexと言うプロパティひとつとっても、かなり掘り下げがいがありました。まだ奥がありそうです。
z-indexをくっきり理解する上での壁がスタッキングコンテキストですが、一度理解できればCSSの理解を深めるための強い武器になると思います。
この記事によって、少しでもz-indexを一発で書く手助けになれば嬉しいです!
最後に完成したコードを載せておくので、おさらいにお使いいただければと思います。
See the Pen
Untitled by yosei (@yosei_1127)
on CodePen.
それでは!👋
参考になったサイト
君は真に理解しているか? z-indexとスタッキングコンテキストの関係 (ICS.media)
【CSS】z-indexの使い方をサンプルで解説!効かない理由は (RAMUNE BLOG))
z-index要素の重なりを管理する方法(スタッキングコンテキスト化で解決) (えるぺぐ)