Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

z-indexがautoと0とで重ね合わせにどんな違いが生じるのかを理解する

More than 1 year has passed since last update.

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 contextMDN訳「現在の重ね合わせコンテキストで作られたボックスの重ね合わせレベル」)は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: autoz-index: 0それぞれの場合に要素のレンダリングがどう違ってくるのかよくわかる例を作ってみました。

サンプルの概要

本記事に使ったサンプルのアニメーションについて、ソースコードはこちら。
DEMO: https://codepen.io/kaz_hashimoto/pen/xxxpmOj

HTMLのツリー構造の主要部分は以下のとおりです。

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を設定し、子要素の位置を互いにずらして配置しました。

00.png

見分けやすいよう上側のbox(b1)にはラベルAを、下側のbox(b2)にはラベルBを表示してあります。さらに子要素の背景色をc1 青#b0cdeb, c2 黄#fff9a5, c3 ピンク#f9b8bcで塗り分けました。

各要素のz-index値はBのみz-index: 0を設定し、ほかはすべて指定なし(初期値auto)です。また、図にはJavaScriptのgetComputedStyle()で取得したz-indexの計算値を表示してあります。この図の段階では、要素の重ね合わせに関してABの表示に差はまだありません。

ちなみに、子要素の重ね順が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の違いはココ!

本題に戻り、最初の状態からABの子要素c2#fff9a5だけを引き出して、それぞれz-indexを-1に設定してみましょう(下図)。すると衝撃の事実がわかります(笑)
01.gif
ご覧のように、z-index: autoAではc2#fff9a5Aの裏側に移動したのに対して、z-index: 0に設定したBでは、c2#fff9a5Bの手前に留まっています。両者で重ね順に違いが生じるのはなぜでしょうか?そのカギは上述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#fff9a5Aより奥に、Bのc2#fff9a5Bより手前になります。

ところで、サンプルの図中に表示している「z-index: 0」などのテキストはp要素で埋め込んでいるため、DOMのノードとしてはBとは別物なので、重ね順の計算上はABなどのboxとは分けて扱われます。たとえば下図で示したように、Bのc2#fff9a5のtop位置を上にずらしていくと、c2のborder+背景+コンテンツのテキストがBのラベル「z-index: 0」の下に潜り込んでしまうのがわかります。(重ね順を見分けやすいようフォントサイズと色も変えました)

02a.gif

これは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が、前節の表で割り出した重ね順に従っていることがわかります(下図)。
02.gif

重ね合わせコンテキストの概念を現実のモノに例えると、付箋を貼った1枚の書類を机に重ねていく様子をイメージするとわかりやすいかもしれません。サンプルの図にあてはめると、

  • #2d2d2dの背景が机の表面
  • 書類ABがあり、それぞれ3枚付箋を貼ってある
  • 書類の表面に付箋用の「重ね合わせコンテキスト」が形成される
  • 付箋にはstack levelが割り当てられ、その書類に貼られる付箋どうしの重なり順を決めている
  • 書類にもstack levelが割り当てられ、机に書類を重ねて置く時の重なり順を決めている

CSSを書いていて、z-indexが効かないように見えたり、難しく感じたりする理由の1つは、「付箋のstack level」と「書類のstack level」が頭の中でごっちゃになってしまうことです。次の例で見てみましょう。

Stack Levelのスコープ

前節の図で、Bとその子要素c1#b0cdeb, c2#fff9a5, c3#f9b8bcのz-indexをすべて整数値に変えてみます(下図)。
03.gif
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もいろいろ変えてみました(下図)。
04.gif
以下の2点に注目です。

  • Aのc1#b0cdeb, c3#f9b8bcのz-index値をいくら大きくしても、重ね順はBとその子要素より上にならない。stack levelは重ね合わせコンテキストをまたがって比較されない。
  • Aのz-indexをautoから整数値に変えると、Aのc2#fff9a5Aの裏側から前面に移動する。Aが新たに重ね合わせコンテキストを生成したため、c2のz-indexはそちらのコンテキスト内でのstack levelになる。

先ほどの「書類と付箋」の関係に例えると、前者は、書類Aに貼った付箋どうしの重ね順を変えているだけであり、重ね順の番号(z-index)にどんなに大きな数字を割り当てても付箋が飛び越えて前面の書類Bの上に混ざることはない、というイメージですね。


CSSを覚えたての頃、私は漠然と「z-indexを大きくすれば最前面に表示されるだろう」と実際やってみてそうならなかったり、z-indexに-1を設定したのにboxがしつこく前面に表示されたりして原因がわからずハマったことが度々ありました。今回サンプルを作ってみて、z-indexの振る舞いに関する疑問が解消しました。

kazhashimoto
HTMLコーダー修行中
https://codepen.io/kaz_hashimoto/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away