要素の重なりについて本気出して考えてみた(z-indexとか何とかとか)

あなたはhtml/cssにおける要素の重なり方を知っていますか?
z-indexを指定しなければ先に書いたほうが下層で後に書けば上層に来る?

というわけで、実は割りと複雑な要素の重なりについて書きます。そこまで意識しなくとも支障がでることは滅多にないと思いますが、知っておいて損はないはずです。
ちなみに、昨今CSS3なるものが流行していますが、この記事はW3CのCSS2.1勧告(と付録)を元に書いています。
基本的な内容であり、目新しいことは特になにもありません。

スタックコンテキストとスタックレベル

用語と概念から。
ある意味、ここが一番よくわかりませんが、ここがわからないと何もわかりません。

スタックコンテキスト(スタッキング・コンテキスト)

簡単に言えばauto以外のz-indexと位置(static以外のposition)を指定した要素によって生成される階層構造を形成する固まりです。ただし、ルート要素(html)はルートスタックコンテキストを整形するので、例えz-indexを一つも指定しなくとも、スタックコンテキストは存在することになり、各ボックスは何らかのスタックコンテキストに属することになります。

スタックコンテキストはその内部に追加のスタックコンテキストを含むことができ、z-indexに整数値を指定すると自身のスタックレベルを0としたローカルスタックコンテキストが生成されます。しかし、スタックコンテキストは各階層における構造によるものなので、あるボックスが同時に複数のスタックコンテキストに股がって属したり、他のスタックコンテキスト内のボックスが任意のボックスの間にくることはできません。

スタックレベル

ボックスは同一スタックコンテキスト内でのz軸の位置となるスタックレベルを持ちます。まぁ、z-indexで指定する数字です。

同じスタックコンテキストに同じスタックレベルを持つボックスがある場合、構造内での順番に従って背面から前面へとボックスが積み重ねられます。

基本的な重なり

基本的に以下のレイヤー順番で重なります(スタックコンテキスト内での子孫の塗装順序)。W3C勧告に倣い、先に書いたものが下層、後に書いたものが上層になります。

  1. スタックコンテキストが整形する要素の背景とボーダー
  2. 負のスタックレベルをもつ子スタックコンテキスト
  3. インラインレベル以外の位置(position)を指定していない要素
  4. 位置(position)を指定していないフロートしている要素とその子孫
  5. インラインレベルの位置(position)を指定していない要素(但しインラインテーブル・インラインブロックを含む)
  6. z-indexがauto又は0の要素とその子孫
  7. 正のスタックレベルを持つ子スタックコンテキストとその子孫

以上の基準に基づきながら、同レイヤーに存在する・同スタックレベルを持つ場合は後から書いたものが上層に、先に書いたものが下層に配置されます。

z-index

注:z-indexはブラウザによるバグが少なくなく以下に書くように動作しない場合があります。

z-indexは垂直位置を指定する

z-indexはボックスの重なりの順序を指定するプロパティであるとよく言われていますが、垂直(z軸の)位置を指定するプロパティと考えた方がよりわかりやすいのではないでしょうか。x軸・y軸の位置をtop(bottom)・left(right)で指定するのと同様にz軸での位置をz-indexで指定するのです(スタックコンテキスト内で)。

よって、x軸・y軸の位置を指定するときと同様にpositonにstatic以外(relative、absolute、fixed)を指定しなければいけません。

z-indexにはpositionが必要⇔positionあるところにz-indexあり

z-indexを指定するには position: static; 以外の指定が必要といいましたが、逆に、position: static; 以外を指定すれば自動的にz-indexが指定されていることにもなります。ある要素に、あなたがposition: static; 以外を指定すると、その要素は自動的にz-indexの初期値である auto が指定されたことになります。

普段意識せずに使っているかもしれませんが、positionを指定した要素がz-indexを指定せずとも以降の要素の下に隠れないのは、これによるものです。

先ほどのリストを思い出して下さい。

「z-indexがauto又は0の要素とその子孫(つまり、positionを指定した要素)」は「正のスタックレベルを持つ子スタックコンテキストとその子孫」を除き、その他の要素より上層のレイヤーとなります。これにより、absoluteやfixedでのナビゲーション固定などをz-indexを指定せずとも自然に実現できているわけです。


実際のコードとサンプル

z-index: auto; と z-index: 0; の違いとスタックコンテキスト

z-index: auto; を指定したボックスのスタックレベルは0であり、上記のようにスタックコンテキストを作成したかのように要素を扱いますが、実際に新しいスタックコンテキストを作成しているわけではなく、親スタックコンテキストの一部と考慮されるます。対して、z-index: 0;はスタックコンテキストを生成します。


どちらも色の薄い方に z-index: -1; を指定しています。
実際のコードとサンプル

z-index: 0; を指定した要素はその指定した要素の背景が最下層となりますが、z-index: auto; を指定した要素はそうではありません。先ほどのリストを見ると、スタックコンテキストの最下層はスタックコンテキストが整形する要素の背景であることから、z-index: auto;を指定した要素は実際には新しいスタックコンテキストを作成しているわけではないことがわかります。

尚、これは::before、::afterといった擬似要素にスタックレベルを指定した場合でも同様の動きをします。擬似要素の元になる要素がスタックコンテキストを生成した場合、::before・::afterに関わらずそのスタックコンテキストに属することになります。よって、CSS3によって流行りとなった紙が捲れたような影を付ける場合などは、元の要素にz-indexを指定して新たなスタックコンテキストができてしまうと、幾ら擬似要素にマイナスのスタックレベルを指定しようとも影が元要素の背景より上に出てしまいますので注意が必要です。


実際のコードとサンプル

新たなスタックコンテキストが出来る出来ないという結果を見てもう一つわかることは、このスタックコンテキストというものがあることにより、z-indexを指定したからといえ完全に好きな階層に置けるわけではないということです。スタックレベルは同じスタックコンテキスト内での順序を決めるものであるため、そのスタックコンテキスト外では機能しません。

例え先祖要素であってもより近い先祖要素にz-indexを指定した場合、その要素の背景より下層に配置することはできませんし、もちろん、先述した通り他のスタックコンテキスト内の任意の層に配置することも出来ません

逆に言えば、余計なところにスタックコンテキストを生成しなければよいわけですから、不必要にz-indexを指定するのを避ければよいということにもなります。

ちなみに、有名なバグとしてIE7ではz-index: auto; でもスタックコンテキストを生成します。割りと困ります。

z-index以外のプロパティのスタックコンテキストの導入 〜 opacity

z-index以外でもスタックコンテキストを導入することが認められており、実際にz-index以外でもスタックコンテキストを生成するプロパティは既に存在します。

例えば、CSS3で加えられた透明度を操作するopacityはスタックコンテキストを生成します。つまり、z-indexを指定しなくとも、opacityで1未満を指定(基準値の 1 ではスタックコンテキストが出来ない)した場合、その要素をスタックレベル0としたスタックコンテキストが生成されてしまうので注意が必要です。


どちらも色の薄い方に z-index: -1; を指定しています。
実際のコードとサンプル

z-index以外の重なり

先ほどのリストで示した通りなのですが、一応サンプルを用意しました。


span → float → p の順に記述。pのみ color: white;
実際のコードとサンプル

p よりも span floatさせた要素の方が先に記述されていますが、リストの通り、spanやfloatさせた要素の背景の方が後に記述された p の背景より上層にきています。
ここで難しいことは、位置(position)指定されていない要素の階層はそれ自体(主に背景とボーダー)のものであり、子孫要素や内部のテキスト等はまた異なるレイヤーに置かれるます。よって、上記の図でもわかるとおり、背景はリストに従い span のものが上層にきていますが、内部のテキストは後から記述された p の内部のものが上層にきています。

正直、この辺の細かい正確なレイヤーは勧告と付録読んでもイマイチ完璧に把握できないところがあります。自分、馬鹿なのかなぁ

このように、後から書いたものだからといって、より上層になるとは限らないのです。(尤も、重なりを意識するのは大抵ブロックレベルの要素なので、ここまで意識する必要があるか微妙ですが。)

おわりに

擬似要素のz-indexの効き方に疑問を覚えて調べていたらこんなになってしまいました。z-indexに関しては「使用頻度が低いために曖昧にしか覚えていなかった結果、ちょっとハマる」なんていうことが無きにしもあらずなのではないかと思います。この際、覚えておいて損はないのではないでしょうか。

ちなみに、ここまで偉そうに解説しましたが、勧告を読んで私がこう理解したというもので、実は違うなんてことが無きにしもあらず…