CSSのz-indexプロパティは何も指定しないと初期値auto
ですが、CSS仕様書9.9.1 Specifying the stack level: the 'z-index' propertyによると、auto
のとき_The stack level of the generated box in the current stacking context_(MDN訳「現在の重ね合わせコンテキストで作られたボックスの重ね合わせレベル」)は0。一方、z-index: 0
と指定すればこれも「現在の重ね合わせコンテキストで作られたボックスの重ね合わせレベル」が0。あれ?auto
の場合と何が違うんでしょう?
- integer
- This integer is the stack level of the generated box in the current stacking context. The box also establishes a new stacking context.
- auto
- The stack level of the generated box in the current stacking context is 0. The box does not establish a new stacking context unless it is the root element.
両者の違いは新しい重ね合わせコンテキストを作るかどうかにあるらしいのですが、説明読んだだけだとイマイチよくわからない。そこで、z-index: auto
とz-index: 0
それぞれの場合に要素のレンダリングがどう違ってくるのかよくわかる例を作ってみました。
サンプルの概要
本記事に使ったサンプルのアニメーションについて、ソースコードはこちら。
DEMO: https://codepen.io/kaz_hashimoto/pen/xxxpmOj
HTMLのツリー構造の主要部分は以下のとおりです。
<div class="b1 box">
<div class="c1 child"></div>
<div class="c2 child"></div>
<div class="c3 child"></div>
</div>
<div class="b2 box">
<div class="c1 child"></div>
<div class="c2 child"></div>
<div class="c3 child"></div>
</div>
このDEMOではアニメーションの各ステップで、z-indexなどのCSSプロパティをJavaScriptからdivにインラインで埋め込んでいます。具体的なCSSプロパティ値については、Chrome DevToolsなどブラウザのインスペクタから確認できます。
z-index値を動かして表示の変化を見る
最初の状態
始めに、DEMOを開いた時の状態です(下図)。要素b1, b2にposition: relative
、子要素c1, c2, c3にposition: absolute
を設定し、子要素の位置を互いにずらして配置しました。
見分けやすいよう上側のbox(b1)にはラベルAを、下側のbox(b2)にはラベルBを表示してあります。さらに子要素の背景色をc1 青#b0cdeb
, c2 黄#fff9a5
, c3 ピンク#f9b8bc
で塗り分けました。
各要素のz-index値はBのみz-index: 0
を設定し、ほかはすべて指定なし(初期値auto)です。また、図にはJavaScriptのgetComputedStyle()
で取得したz-indexの計算値を表示してあります。この図の段階では、要素の重ね合わせに関してAとBの表示に差はまだありません。
ちなみに、子要素の重ね順がc3#f9b8bc
> c2#fff9a5
> c1#b0cdeb
になるのは、DOMを「行きがけ順に深さ優先」のtree orderで辿ることにより、HTMLで後ろに書いた方の要素が手前に描画されるためです。
参考) CSS仕様書Appendix E. Elaborate description of Stacking Contexts
Preorder depth-first traversal of the rendering tree,
およびPainting order #8
8. All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order.(以下略)
autoと0の違いはココ!
本題に戻り、最初の状態からAとBの子要素c2#fff9a5
だけを引き出して、それぞれz-indexを-1に設定してみましょう(下図)。すると衝撃の事実がわかります(笑)
ご覧のように、z-index: auto
のAではc2#fff9a5
がAの裏側に移動したのに対して、z-index: 0
に設定したBでは、c2#fff9a5
がBの手前に留まっています。両者で重ね順に違いが生じるのはなぜでしょうか?そのカギは上述Appendix EのPainting order #8にありそうです。
(中略) For those with 'z-index: auto', treat the element as if it created a new stacking context, but any positioned descendants and descendants which actually create a new stacking context should be considered part of the parent stacking context, not this new one. For those with 'z-index: 0', treat the stacking context generated atomically.
まず、9.9.1によると、基本的に
- root要素(背景黒
#2d2d2d
)は、_root stacking context_を形成する。 - 各boxはどれか1つの重ね合わせコンテキストに所属する。
そこで、各boxの「現在の重ね合わせコンテキスト」=「所属する重ね合わせコンテキスト」に着目して項目の関係を分析すると下表のようになります。
(SC: Stacking Context)
box | z-index | stack level | 新SCの生成 | 生成したSC a | 所属SC |
---|---|---|---|---|---|
A | auto | 0 | No | - | root SC |
Aのc2#fff9a5
|
-1 | -1 | Yes | sc1 | root SC b |
Aのc1#b0cdeb , c3#f9b8bc
|
auto | 0 | No | - | root SC |
B | 0 | 0 | Yes | sc2 | root SC c |
Bのc2#fff9a5
|
-1 | -1 | Yes | sc3 | sc2 c |
Bのc1#b0cdeb , c3#f9b8bc
|
auto | 0 | No | - | sc2 |
a) 「生成したSC」と「所属SC」との紐付けがわかるように、便宜上識別子を付けました。
b) 上述の「... should be considered part of the parent stacking context」より、親要素Aの所属SCの一部とみなされる。
c) 上述の「... For those with 'z-index: 0', treat the stacking context generated atomically」より、Bが作る重ね合わせコンテキストsc2は不可分であり、Aのようにc2を分離して扱わない。
そして、重ね順の決め方は9.9.1によると、
(中略) an integer stack level, which is its position on the z-axis relative other stack levels within the same stacking context. (中略) Boxes may have negative stack levels. Boxes with the same stack level in a stacking context are stacked back-to-front according to document tree order.
とのことなので、所属SCが同じグループのboxどうしのstack levelを比較して、tree orderも考慮すると最終的な重ね順は以下のようになります。(番号が小さい方がユーザーから見て奥側)
|重ね順| box | z-index | stack level | 生成SC | 所属SC |
|--:|:--|:--:|:--:|:--:|:--:|:--:|
|1| Aのc2#fff9a5
| -1 | -1 | sc1 | root SC |
|2| A | auto | 0 | - | root SC |
|3| Aのc1#b0cdeb
| auto | 0 | - | root SC |
|4| Aのc3#f9b8bc
| auto | 0 | - | root SC |
|5| B | 0 | 0 | sc2 | root SC |
|6| Bのc2#fff9a5
| -1 | -1 | sc3 | sc2 |
|7| Bのc1#b0cdeb
| auto | 0 | - | sc2 |
|8| Bのc3#f9b8bc
| auto | 0 | - | sc2 |
確かにAのc2#fff9a5
はAより奥に、Bのc2#fff9a5
はBより手前になります。
ところで、サンプルの図中に表示している「z-index: 0」などのテキストはp要素で埋め込んでいるため、DOMのノードとしてはBとは別物なので、重ね順の計算上はAやBなどのboxとは分けて扱われます。たとえば下図で示したように、Bのc2#fff9a5
のtop位置を上にずらしていくと、c2のborder+背景+コンテンツのテキストがBのラベル「z-index: 0」の下に潜り込んでしまうのがわかります。(重ね順を見分けやすいようフォントサイズと色も変えました)
これは9.9.1の以下の説明のとおり、Bの子孫どうしの中で、位置指定されてないp要素の方が、z-index: -1
のc2#fff9a5
より描画順が後ろ(手前に)になるためです。
Within each stacking context, the following layers are painted in back-to-front order:
1. the background and borders of the element forming the stacking context.
2. the child stacking contexts with negative stack levels (most negative first).
3. the in-flow, non-inline-level, non-positioned descendants.
(略)
このDEMOでは重ね順を視覚的にわかりやすいように、boxとラベルのテキストを一体であるかのように表示していますが、より正確にはboxとは「div要素のbackground+border」を指しており、コンテンツは含まれません。
表題の「autoと0の違い」については以上で明確になりました。ついでに、このDEMOを使ってz-indexのそのほかの振る舞いも見ていきたいと思います。
重ね合わせコンテキストをまるごと移動
前節に図示した状態から、Bの位置を移動して矩形の一部をAに重ねてみましょう。8個のboxが、前節の表で割り出した重ね順に従っていることがわかります(下図)。
重ね合わせコンテキストの概念を現実のモノに例えると、付箋を貼った1枚の書類を机に重ねていく様子をイメージするとわかりやすいかもしれません。サンプルの図にあてはめると、
- 黒
#2d2d2d
の背景が机の表面 - 書類AとBがあり、それぞれ3枚付箋を貼ってある
- 書類の表面に付箋用の「重ね合わせコンテキスト」が形成される
- 付箋にはstack levelが割り当てられ、その書類に貼られる付箋どうしの重なり順を決めている
- 書類にもstack levelが割り当てられ、机に書類を重ねて置く時の重なり順を決めている
CSSを書いていて、z-indexが効かないように見えたり、難しく感じたりする理由の1つは、「付箋のstack level」と「書類のstack level」が頭の中でごっちゃになってしまうことです。次の例で見てみましょう。
Stack Levelのスコープ
前節の図で、Bとその子要素c1#b0cdeb
, c2#fff9a5
, c3#f9b8bc
のz-indexをすべて整数値に変えてみます(下図)。
Bとc2#fff9a5
に着目すれば、「重ね合わせコンテキスト」と呼ばれる理由がよくわかります。Bのz-indexを100に増やして、c2#fff9a5
のz-indexを-200に大きく減らしても、c2は依然Bの上に描画されます。これはstack levelが比較される対象(コンテキスト)が両者で異なるためです。
- c2
#fff9a5
のstack level=-200が比較される対象は、Bが生成した重ね合わせコンテキスト内の要素c1, c2, c3のstack level。結果、それらの中で最小値を持つc2がスタックの最下位に描画される。 - Bのstack level=100が比較される対象は、Bが所属する重ね合わせコンテキスト内の要素(Aや、Aのc2)のstack level。親要素と子要素のz-indexを比較しても意味がない。
z-indexが及ぶ範囲
最後に、Aとその子要素のz-indexもいろいろ変えてみました(下図)。
以下の2点に注目です。
-
Aのc1
#b0cdeb
, c3#f9b8bc
のz-index値をいくら大きくしても、重ね順はBとその子要素より上にならない。stack levelは重ね合わせコンテキストをまたがって比較されない。 -
Aのz-indexをautoから整数値に変えると、Aのc2
#fff9a5
がAの裏側から前面に移動する。Aが新たに重ね合わせコンテキストを生成したため、c2のz-indexはそちらのコンテキスト内でのstack levelになる。
先ほどの「書類と付箋」の関係に例えると、前者は、書類Aに貼った付箋どうしの重ね順を変えているだけであり、重ね順の番号(z-index)にどんなに大きな数字を割り当てても付箋が飛び越えて前面の書類Bの上に混ざることはない、というイメージですね。
CSSを覚えたての頃、私は漠然と「z-indexを大きくすれば最前面に表示されるだろう」と実際やってみてそうならなかったり、z-indexに-1を設定したのにboxがしつこく前面に表示されたりして原因がわからずハマったことが度々ありました。今回サンプルを作ってみて、z-indexの振る舞いに関する疑問が解消しました。