1. kazhashimoto

    Posted

    kazhashimoto
Changes in title
+z-indexがautoと0とで重ね合わせにどんな違いが生じるのかを理解する
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,150 @@
+CSSのz-indexプロパティは何も指定しないと初期値`auto`ですが、CSS仕様書[9.9.1 Specifying the stack level: the 'z-index' property](https://drafts.csswg.org/css2/visuren.html#propdef-z-index)によると、`auto`のとき_The stack level of the generated box in the current stacking context_([MDN訳](https://developer.mozilla.org/ja/docs/Web/CSS/z-index)「現在の重ね合わせコンテキストで作られたボックスの重ね合わせレベル」)は0。一方、`z-index: 0`と指定すればこれも「現在の重ね合わせコンテキストで作られたボックスの重ね合わせレベル」が0。あれ?`auto`の場合と何が違うんでしょう?
+> <dl>
+> <dt>integer</dt>
+> <dd>This integer is <em>the stack level of the generated box in the current stacking context</em>. The box also establishes a new stacking context. </dd>
+> <dt>auto</dt>
+> <dd><em>The stack level of the generated box in the current stacking context</em> is 0. The box does not establish a new stacking context unless it is the root element. </dd>
+> </dl>
+
+両者の違いは新しい重ね合わせコンテキストを作るかどうかにあるらしいのですが、説明読んだだけだとイマイチよくわからない。そこで、`z-index: auto`と`z-index: 0`それぞれの場合に要素のレンダリングがどう違ってくるのかよくわかる例を作ってみました。
+
+## サンプルの概要
+本記事に使ったサンプルのアニメーションについて、ソースコードはこちら。
+DEMO: [https://codepen.io/kaz_hashimoto/pen/xxxpmOj](https://codepen.io/kaz_hashimoto/pen/xxxpmOj)
+
+HTMLのツリー構造の主要部分は以下のとおりです。
+
+```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](https://codepen.io/kaz_hashimoto/pen/xxxpmOj)ではアニメーションの各ステップで、z-indexなどのCSSプロパティをJavaScriptからdivにインラインで埋め込んでいます。具体的なCSSプロパティ値については、Chrome DevToolsなどブラウザのインスペクタから確認できます。
+
+## z-index値を動かして表示の変化を見る
+### 最初の状態
+始めに、[DEMO](https://codepen.io/kaz_hashimoto/pen/xxxpmOj)を開いた時の状態です(下図)。要素b1, b2に`position: relative`、子要素c1, c2, c3に`position: absolute`を設定し、子要素の位置を互いにずらして配置しました。
+
+![00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/8b16611e-5cdc-1acd-98d6-92a5d0d6e2ca.png)
+
+見分けやすいよう上側の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](https://drafts.csswg.org/css2/zindex.html)
+
+> 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に設定してみましょう(下図)。すると衝撃の事実がわかります(笑)
+![01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/5b9d0709-1f51-8d01-9d43-254c1c10ca80.gif)
+ご覧のように、`z-index: auto`の**A**ではc2`#fff9a5`が**A**の裏側に移動したのに対して、`z-index: 0`に設定した**B**では、c2`#fff9a5`が**B**の手前に留まっています。両者で重ね順に違いが生じるのはなぜでしょうか?そのカギは上述[Appendix E](https://drafts.csswg.org/css2/zindex.html)の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](https://drafts.csswg.org/css2/visuren.html#propdef-z-index)によると、基本的に
+
+* root要素(背景黒`#2d2d2d`)は、_root stacking context_を形成する。
+* 各boxはどれか1つの重ね合わせコンテキストに所属する。
+
+そこで、各boxの「現在の重ね合わせコンテキスト」=「所属する重ね合わせコンテキスト」に着目して項目の関係を分析すると下表のようになります。
+(SC: Stacking Context)
+
+| box | z-index | stack level | 新SCの生成| 生成したSC <sup>a</sup> | 所属SC |
+|:--|:--:|:--:|:--:|:--:|:--:|
+| **A** | auto | 0 | No | - | root SC |
+| **A**のc2`#fff9a5` | -1 | -1 | Yes | sc1 | root SC <sup>b</sup> |
+| **A**のc1`#b0cdeb`, c3`#f9b8bc` | auto | 0 | No | - | root SC |
+| **B** | 0 | 0 | Yes | sc2 | root SC <sup>c</sup> |
+| **B**のc2`#fff9a5` | -1 | -1 | Yes | sc3 | sc2 <sup>c</sup> |
+| **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](https://drafts.csswg.org/css2/visuren.html#propdef-z-index)によると、
+
+> (中略) 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」の下に潜り込んでしまうのがわかります。(重ね順を見分けやすいようフォントサイズと色も変えました)
+
+![02a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/4c71fe49-5937-7342-e2d6-281ebf80cce1.gif)
+
+これは[9.9.1](https://drafts.csswg.org/css2/visuren.html#propdef-z-index)の以下の説明のとおり、**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](https://codepen.io/kaz_hashimoto/pen/xxxpmOj)を使ってz-indexのそのほかの振る舞いも見ていきたいと思います。
+
+### 重ね合わせコンテキストをまるごと移動
+前節に図示した状態から、**B**の位置を移動して矩形の一部を**A**に重ねてみましょう。8個のboxが、前節の表で割り出した重ね順に従っていることがわかります(下図)。
+![02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/5ea9236e-ab69-1f22-823a-0b9ccf4ee1b4.gif)
+
+重ね合わせコンテキストの概念を現実のモノに例えると、**付箋を貼った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をすべて整数値に変えてみます(下図)。
+![03.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/c2c6fdd4-5e86-975a-e453-1518b00f184b.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/f0f4a711-d0b2-856c-b92b-56d786ea4156.gif)
+以下の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の振る舞いに関する疑問が解消しました。