解 説

z-indexは要素の重なり順を変更することができる便利なプロパティですが、思わぬところで落とし穴があります。
z-indexの値を変更しても重なり順を変更できずに困った経験がないでしょうか。その原因は正しい仕組みを理解していないためです。

z-indexとは単純に要素の重なり順を変更するだけのものではなく、スタックコンテキストが形成された後にスタックレベルを表すものです。スタックコンテキスト、スタックレベルとは何かを理解して、正しい使い方ができるようになりましょう。
そうすると、要素の重なり順を変更することも正確にできるようになります。

スタックコンテキストとは

スタックとは、最後に入力したデータが先に出力される後入れ先出しの構造のことです。
コンテキストは文脈の意味がありますが、この場合はあるスタックのグループを意味します。もっと具体的に言うと兄弟関係にある要素のグループです。
親子関係になると別の流れのスタックになるわけです。
ブロック要素で形成される各ボックスは、いずれかのスタック コンテキスト(stacking context)に属しています。
HTMLの要素で使われているスタック コンテキストとは、positionをstatic以外に設定をした要素をベースとした兄弟要素の集合を指します。更にopacity を指定した要素も同様にスタック コンテキストが生成されます。また、transform を指定した要素も同様にスタック コンテキストが生成されます。

コンテキスト(文脈)という用語はz-indexのみならず、スクリプトでも出てきます。またコンテンツマーケティングの説明でも登場します。そしてコンテキストがでてくると途端に意味不明の状況に陥ります。これはコンテキストの意味が文脈と非常にあいまいな表現のためだと思われます。
そもそも、文脈って何でしょうか?簡単な例で考えてみましょう。
一休さんの有名な話にヒントがあります。「このはし渡るべからず」という立て札を無視して一休さんは、橋の真ん中を堂々と渡ります。これは「橋」を「端」と理解したとするトンチです。もし、この立て札が次のような内容だったらどうでしょうか。「このはしは先日の台風で損傷が激しくて壊れそうです。このはしわたるべからず」となっていたら、この橋は危険だから渡るなと理解できます。あるいは「このはしの両端は壊れています。真ん中を歩けば大丈夫です。このはしわたるべからず」としたら端っこは通らない方がよさそうなので真ん中を選んで渡ることでしょう。前後の文章次第で解釈が変わってきますよね。つまりこの前後の文が文脈(コンテキスト)になります。コンテキスト次第でその後の命令の意味が変わるような場合に便利な仕組みとしてスクリプトなどの世界でも文脈(コンテキスト)は重要視されています。

スタック コンテキストは、スタックに積み重なったひとつのグループだと考えることができます。

ルート要素(html 要素)は何もしなくても、ルートスタックコンテキストを生成します。そのため、どの要素も初期状態ではルートスタックコンテキストに所属します。

スタック コンテキストを作成する方法をまとめると

  1. ルート要素(html)が形成する「ルートスタックコンテキスト」はあらかじめ作成されます。
  2. 位置指定されていて(absolute か relative)、z-index 値が “auto” 以外の要素
  3. z-index:0もスタックコンテキストを作成します。
  4. 1 未満の opacity 値を持つ要素
z-indexを要素に設定できる条件は以下のものがあります。

  • static以外のposition指定がされている
  • opacity指定がされている
  • transform指定がされている

スタックレベルとは

ブロック要素で形成される各ボックスの重ね順は、そのコンテキスト(グループ)におけるスタックレベル(stack level)という整数値で表すことができます。
つまり、先入れ後出しに積まれた要素の順番がスタックレベルといえます。

このスタックレベルが、同じコンテキスト内の他のボックスに対する、Z軸上の相対的な位置を示しています。 スタックレベルが大きいボックスは、スタックレベルが小さいボックスより常に前面に表示されます。
また、負のスタックレベルも使用できます。
同じコンテキストに同じスタックレベルを持つボックスがある場合、ツリー構造内での順番に従って背面から前面へとボックスを積み重ねていきます。

つまり、要素はスタックな重なりをし、その重なり順がスタックレベルであり、z-indexで指定できるということです。また、それらはグループを形成するため重なり順はそのグループ内でしか通用しないものです。

簡単にまとめると次のようになります。

  • スタックレベルとはz-indexの値で決まる重なり順です。
  • z-index は、同じスタック コンテキスト内の重なり順序の指定です。
  • 別のスタック コンテキスト内の要素に対しては重なり順序を指定できません。

zindex2

注意すべき点として、あるスタック コンテキスト内(スタックグループ)の要素に対しては、スタックレベルは同一コンテキスト(同一グループ)内だけで通用するもので、別のグループ内の順番を使って重なり順序を指定できないことです。
これを知らないとz-indexを指定しても重なり順が変わらないと悩むハメになります。重ね順がz-indexの値で思うように変わらない原因の多くはここにあります。

次の実例でその仕組みを理解しましょう。

z-indexの実例

z-indexを使って重なり順を変更するときに、つまずくポイントは親子関係の要素を入れ替えるときです。
通常は親要素の上に子要素が重なりますが、この重なり順を変更するには親要素のpositionはstaticである必要があります。
親要素のz-indexを2にして子要素を1にしても重なり順は変わりません。その理由は先程からの説明にあるとおり、スタックコンテキストが違うからです。
親要素と子要素の重なり順を変更するには、親要素をpositionはstaticにして子要素のz-indexをマイナスにすることで実現できます。

z-index1

これらの挙動をサンプルで確認してみましょう。
ここからのサンプルは基本的に同じ構造のものですが、サンプルの結果を見やすいようにCSSで多少上下左右に要素を動かしたりしています。

通常フローの例

次のサンプルは、クラスa~クラスfまでのdiv要素が通常フローで並んでいます。ポイントはクラスdのdivだけクラスcの子要素ななっている点です。親子関係にあるのはcが親でdが子です。

z0
スタックコンテキストの例
このサンプルはpositionの指定がありませんので、全ての要素はルート スタック コンテキストに属します。
HTMLコード

CSSコード

各要素にposition:absoluteを指定した場合

次に、HTMLは先ほどと同様で、各要素にCSSでposition:absoluteの設定をおこないます。更に各要素にtopの指定をしてそれぞれの要素が重なるようにCSSを設定しました。
このとき、注目すべき点は、ピンク色のクラスdのdiv要素は赤色のクラスcのdiv要素のスタック コンテキストに属しますので他のdiv要素とは別のスタックコンテキストに属することになります。
ここではピンク色のクラスdのdiv(つまり親要素cと子要素d)の挙動に注目してみることにします。

z1
サンプル2
サンプルを見たとおり、クラスaの上にクラスbが重なり、その上にクラスcが順番に重なります。ルートスタックコンテキストの順番に重なっていきます。
クラスdはクラスcの子要素になりますので、他のdivとは別のスタックコンテキストを形成しています。
けれども結果は変わらずに、あたかもクラスcの上にクラスdが重なり、クラスdの上にクラスeが重なったようになります。(今後ここでの.a,.b,.c,.d,.e,.fの表記はクラスaやクラスb….の事とします)
本来のルートスタック コンテキストが.cの次は.eとなりますが、.dは.cが作ったスタックコンテキストになるため.cと.dは.cにグループ化されたような存在となり.cと.dの上に.eが重なった状態になっています。

z-indexを指定しないで、positionを使用してそれぞれの要素を重ねた場合はルートスタックコンテキストのスタックレベルに従って重なり合います。たとえ要素の親子関係が生じても新たなスタックが生じても、親子関係は親要素のグループとしてルートスタックコンテキストのレベルに従った重なり順になります。

CSSのコード

各要素にz-indexを設定する

次のサンプルは、それぞれa〜eまでのクラスにz-indexを設定したものです。
ルートスタックコンテキストのスタックレベルに従ってz-indexの値を.aを1として.fを6とします。
z1
サンプル3
.aが一番奥になり、.fが最前面に重なります。

各要素のz-indexを変更してみる

先ほどのサンプル2、3と逆順になるようにz-indexの値を逆順に設定しました。
結果として一番最初の緑色の.a要素が一番上になり一番最後の.f要素のみかん色が一番下に位置します。
z2
サンプル4

ここで注目すべき点は.dの挙動です。.dのピンクは.cの赤より上にあります。ルートスタックコンテキストに従えば赤がピンクの上になるはずです。これは.dがルートスタックコンテキストではなく、.cのスタックコンテキストであることを示しています。

つまり単純にz-indexを変えても子要素と親要素の重なり順は変更できないということです。
CSSのコード

子要素のz-indexの値を0にしてみる

ここで、さらに確認するためにピンク色.dのz-indexの値を0にしてみます。
z1
サンプル5
ピンク色の.dをz-index:0;とすることで一番最背面にするように考えてしまいますが、けれども、結果はサンプル3と同じ結果になります。
これも.dが.cのスタックコンテキストである証拠です。.cのz-indexは他のdivのz-indexとは比較できないのです。
ピンク色の.dをz-indexはあくまでも新しいスタックコンテキストである.cの中だけでしか通用しないものです。

z-indexで重なり順がうまく操れない原因はここにあります。

CSSコード

子要素をルートスタックコンテキストに参加させる

次に.cをposition:staticにしてルートスタックコンテキストから外します。
親要素のcをスタックコンテキストから外すことで子要素dはルートスタックコンテキストに参加できます。
更に.dのz-indexの値を2にしました。
.cの赤は最背面に移り、.dは.aの次に重なります。.dはスタック コンテキストから外れた.cの赤を無視してルートスタックコンテキストに参加しています。
z3
サンプル6

子要素をルートスタックコンテキストに参加させるには親要素をposition:staticにする方法がある

CSSコード

z-indexに負の値を設定

もう少し挙動を観察してみます。
.cをposition:staticとしてルートスタックコンテキストから外した状態のままです。
.aのz-index:1として順番にz-indexの値を増やして.fのz-index:6としています。
ここで子要素であるピンク色.dのz-index:-1とするとどうなるでしょうか。
z4
サンプル7

結果はサンプル7のとおり.dのピンクがposition:staticにした親要素.cよりもさらに最背面になります。
z-indexの値を負の値にするとスタックコンテキストに参加してない要素の更に背面に移動することになります。
これは、.cがある意味z-index:0と同等になったと考えたくなりますが、z-index:0とposition:staticは全く異質のもので混同してはいけません。
z-index:0はスタックコンテキストを作成しますが、position:staticはスタックコンテキストを作成しない特徴があります。
このサンプルでは.cがposition:staticなため、スタックコンテキストを作成してないところがポイントです。

CSSコード

綺麗に逆順に並べる

A〜Fまできれいに逆順F〜Aの順に並べるにはcの設定がポイントになります。
.cには子要素のdがあります。子要素.dをルートスタックコンテキストに参加させるには、親要素.cをスタックから外さないといけません。そのためにまず、.cのpositionをstaticにします。
.cより上に重ねたい.aと.bのz-indexの値は.cの値を0と同等と考えて1以上に設定します。
また.cより下に重ねたい.d以降のオブジェクトはマイナスの値を設定します。

サンプル
CSSコード

追記
また、z-indexの値をautoにすると親要素と同じ階層になりますので、positionを使用したままposition:staticと同じ状況をつくることができます。
z-index:autoはz-indexを省略した場合のデフォルト値です。z-indexを省略しても同様になります。

positionがstarticでなければ、z-index=0にしてもスタックコンテキストを作成する

例えばサンプル7の.cのposition:staticをposition:absolute;z-index:0;とすると結果は大きく変わります。
親要素はルートスタックコンテキストに参加し、また新たに子要素のスタックコンテキストを作成しています。
そして新しいスタックコンテキストの中でz-indexの値をマイナスの値にしてもそのスタックコンテキストの中の指定になるため、親の下に移る事はありません。
親要素はz-index:0としたためルートスタックコンテキストの最背面に移動し、その中の子要素という位置づけでピンク色の.dは.cの次に位置することになっています。
z7
サンプル8
図が見にくいですが、赤の上にピンクがあり、その上に緑、青、橙、みかん色の順番になります。

opacityプロパティでスタックコンテキスト作成

次にopacityプロパティがスタックコンテキストを作成することを確かめてみましょう。

先ほどのサンプル8から.cのpositionを再びstaticに戻します。
そしてその代わりにopacityプロパティを設定します。
わかりやすいように多少要素の位置をずらしていますが基本的に同じ構造です。
結果はサンプル8と同じ結果になります。つまりopacityプロパティが新たなスタックコンテキストを作成したことが確かめられます。

z8
サンプル9
CSSコード

z-indexをとくに変更してないにもかかわらず、重なり順が突然変わってしまった。という事象はこの辺が原因になっている可能性があります。

実例サンプル

z-index応用サンプル
上のボタンの形がマウスホバーすると下のようになります。
z1

.shadow は背景にあたるピンクの四角形を作ります。
これを基準にするために、 position: relative として z-index: 0 としています。

.btn はボタン部分になります。
スタックをつくるために、position: relativeを指定します。

.btn:before, .btn:afterは疑似要素を使って .btn の前後に空白のオブジェクトを作成します。これが左右の影の部分になります。
width:30%とすることでボタンの幅に対して30%の幅としています。
さて、この影部分は、z-index: -1 としていますが、これは.btnより下に重ねるためです。
この記述がポイントです。 .btn には z-index の記述がありません。記述を省略すると z-index:auto と同じ意味になります。
「.btn」 と 「.btn:before, .btn:after」 は親子関係の状態にあります。親子の関係では z-index の数値の大きさで重ね順を変えることはできません。それは上述したスタックの関係が起因しているためです。
スタックを乗り越えて重ね順を変えるには親要素を z-index:auto として子要素の z-index を -1 とすることで解決できます。
.btn:hover と .btn:hover:before でマウスホバーしたときのボタンの影を描きます。この場合は影を中央部分に持ってきて膨らみを持たせたように見せています。
.btn:hover:after はマウスホバーしたときには、:after の設定は不必要になるためにdisplay:noneで表示を消しています。

HTML

CSS