スタッキングコンテキストとz-indexの合成順序
要素が前後する理由を毎回ブラウザと同じ手順で説明でき、z-indexが効かない事故を当て推量せず直せる。生成条件とペイント順序を原理から解き明かします。
- 1.重なり順はz-index単独でなく、まず祖先のスタッキングコンテキストの入れ子で大枠が決まり、各コンテキスト内部だけでz-indexが比較される。親が下なら子をいくら上げても親同士の順位は覆らない。
- 2.スタッキングコンテキストはroot要素のほか、positionとz-index指定の組、opacity1未満、transform/filter、will-change、isolation:isolateなどが生成する。生成された瞬間にその部分木は独立した重なり単位になる。
- 3.1つのコンテキスト内部のペイント順序は7段の決定木で固定され、背景→負z-index→ブロック→フロート→インライン→z-index:0/auto→正z-indexの順に下から積む。同段内は出現順がタイブレーク。
重なり順は「コンテキストの入れ子」が先、z-indexは後
要素の前後関係を決めるのは z-index の数値ではなく、まずスタッキングコンテキスト(stacking context)という入れ子構造です。スタッキングコンテキストとは、内部の要素群を1つの独立した重なり単位として扱う区画で、画面という奥行き軸(z軸)に沿った積み重ねは、この単位をツリー状に入れ子で解決します。比較は次の二段構えです。
1. ある要素とある要素の重なりは、まず共通の祖先スタッキングコンテキストまで遡る
2. その共通祖先の中で「どちらの子コンテキストが上か」を決め、勝った側の部分木がまるごと上に来る
3. z-index 同士の比較は「同じスタッキングコンテキストに直接属する要素どうし」でしか行われない
ここに「z-index: 9999 を付けたのに前に出ない」の正体があります。比較相手と別のスタッキングコンテキストに属していて、その親コンテキスト同士が既に順序を決めているなら、子の z-index をいくつ盛っても親の順位は覆りません。重なりは数値の大小ではなく、入れ子のどの階層で差がついたかで決まる辞書式(lexicographic)の構造です。詳細度を辞書式に比べる CSSカスケード・詳細度 と同じく、上位の段で勝負が付いたら下位の数値は見られません。
z-index の値はグローバルな絶対順位ではなく、所属するスタッキングコンテキスト内だけで有効なローカルな順番です。別コンテキストの z-index: 1 と z-index: 9999 を直接比べることはできません。両者の前後は、それぞれが属するコンテキストの祖先がどこで分岐し、その分岐点での順位がどうかで決まります。
スタッキングコンテキストの生成条件
どの要素がスタッキングコンテキストを生成するかを知らないと、入れ子構造が読めません。生成条件は仕様の各所に分散していますが、実務で踏むものは次に集約できます。生成された瞬間、その要素を根とする部分木は外から見て1つの塊になります。
| 生成する条件 | 補足 | 落とし穴 |
|---|---|---|
| root要素(html) | ページ全体の最上位コンテキスト | すべての基準点 |
| position が absolute/relative かつ z-index が auto以外 | 古典的な生成条件 | z-index:auto では生成しない |
| position が fixed/sticky | z-index 不要で常に生成 | fixed は z-index なしでも独立する |
| opacity が 1未満 | 0.99 でも生成 | 意図せぬ生成の最頻ケース |
| transform / filter / perspective が none以外 | translateZ(0) 含む | GPU昇格と同時にコンテキスト化 |
| will-change に上記系プロパティ | transform/opacity等を指定 | 宣言しただけで生成 |
| isolation が isolate | 副作用なく生成する専用値 | z-index 事故の予防に有効 |
| mix-blend-mode が normal以外 / contain:paint等 | 合成・包含に関わる値 | 見落としやすい |
注意すべきは、opacity: 0.99 や transform: translateZ(0) のように見た目をほとんど変えない指定でもコンテキストが生成されることです。性能目的で付けた will-change: transform が、知らぬ間に部分木を独立させ、子孫の z-index の効く範囲を狭めます。GPU合成レイヤーへの昇格を扱う レンダリングパイプライン詳説 と重なるのはこのためで、合成レイヤー化とスタッキングコンテキスト生成はしばしば同じプロパティが引き金になります。
かつてスタッキングコンテキストは「position + z-index」が代表的な生成条件でしたが、現在は opacity・transform・filter・will-change・isolation など position と無関係な条件が多数あります。「position: static だから安全」という前提は崩れています。親に opacity: 0.95 が1つあるだけで、その内側の z-index は外へ届かなくなります。
1コンテキスト内部のペイント順序:7段の決定木
スタッキングコンテキストが1つ与えられたとき、その内部の要素をどの順で奥から手前へ積むかは、CSS2.2 が定める固定の決定木で決まります。下に挙げた順で下(奥)から上(手前)へ描かれ、後に描くものが手前に来ます。
1. このコンテキストを生成した要素自身の背景・ボーダー(最も奥)
2. z-index が負の子スタッキングコンテキスト(値の小さい順)
3. 通常フローの非インライン・非ポジション要素(ブロックボックス)
4. フロート(float)した要素
5. 通常フローのインラインレベル要素
6. z-index が auto または 0 のポジション要素・子コンテキスト
7. z-index が正の子スタッキングコンテキスト(値の大きいほど手前)
この7段は固定で、z-index が効くのは2段目(負)と6段目(0/auto)と7段目(正)の相対順を動かすときだけです。重要な帰結が2つあります。第一に、負の z-index を持つ子は、親の背景(1段目)より手前だが、親の通常フロー内容(3段目以降)より奥に潜ります。これが「z-index: -1 の要素が親の背景の上・テキストの下に挟まる」挙動の根拠です。第二に、ポジションされていない通常フローのブロックは3段目に固定で、z-index を持てません。だから z-index を効かせたい要素には position(または transform 等)が要ります。
z-index: 0 はスタッキングコンテキストを生成する(position付きの場合)のに対し、z-index: auto は生成しない——どちらもペイント順序の段(6段目)は同じです。つまり見かけの重なりは同じでも、z-index: 0 を付けた要素の内側では子孫の z-index が外へ漏れなくなります。「0 にしたら子の重なりが変わった」の正体はこの生成有無の差です。
なぜz-indexが「効かない」のか:境界の見抜き方
「z-index が効かない」相談はほぼ次の3パターンに帰着します。原理が分かれば切り分けは機械的です。
| 症状 | 原因 | 確認・対処 |
|---|---|---|
| 大きな値にしても前に出ない | 比較相手と別コンテキストで、親同士で既に決着 | 祖先を遡り opacity/transform/filter を持つ要素を探す |
| z-index が無視される | position も transform 等もなく段3に固定 | position:relative 等を付けて段を移す |
| 子に値を盛っても親を超えない | 親コンテキストの順位が天井になる | 親側の z-index を上げるか入れ子を解消する |
| 意図せず重なりが変わった | opacity:0.99 や will-change が新コンテキストを生成 | isolation:isolate で意図を明示し範囲を固定 |
切り分けの第一手は対象要素の祖先を root まで遡り、スタッキングコンテキストを生成している要素を列挙することです。生成要素の連なりが入れ子の階層であり、2要素の重なりはその最も近い共通祖先コンテキスト内での順位に還元されます。比較相手と異なる枝に分かれた時点で、各枝の根(子コンテキスト)の順位が天井になり、内部の z-index はその天井を超えられません。
モーダルやドロップダウンが「最前面に出ない」典型は、祖先のどこかに transform(アニメーションやGPU昇格目的)や opacity 未満(フェード演出)があり、そこで新たなスタッキングコンテキストが生まれて部分木が閉じ込められているケースです。position: fixed でさえ、transform を持つ祖先がいればその祖先を包含ブロックの基準にし、コンテキストにも閉じ込められます。最前面オーバーレイは、こうした天井を持たない root 直下や、<dialog> の top layer(スタッキングコンテキストの枠組みの外で最前面に出る別機構)へ逃がすのが堅実です。
isolationで重なりを設計する
isolation: isolate は、副作用なくスタッキングコンテキストだけを生成するための専用値です。opacity や transform のように見た目を変えず、純粋に「ここから内側を1つの重なり単位として閉じる」意図を宣言できます。これはコンポーネント設計で効きます。再利用部品の根に isolation: isolate を置けば、その内部の z-index 戦争は外部のグローバルな z-index 体系と完全に隔離され、z-index: 2 程度の小さな値で部品内を組み立てつつ、外への漏れを防げます。
.component-root { isolation: isolate; } /* 内部の z-index は外へ漏れない */
.component-root .tooltip { z-index: 10; } /* この 10 はコンポーネント内ローカル */
逆に言えば、意図せぬコンテキスト生成こそが事故の温床です。設計としては「重なりを閉じたい境界には明示的に isolation: isolate を置き、それ以外では opacity や transform を不用意に親へ付けない」が定石になります。重なりの設計は、セレクタやボックスモデルの土台である CSS と、サイズ確定後の描画段である リフローとリペイントの分類 の中間にある、描画順(paint order)の層を扱っていると捉えると位置づけが明確になります。
スタッキング問では、(1) 重なりはスタッキングコンテキストの入れ子で大枠が決まり、z-index 比較は同一コンテキスト内に限られる辞書式構造である点、(2) 生成条件が position+z-index だけでなく opacity 未満・transform/filter・will-change・isolation:isolate など多数ある点、(3) 内部ペイント順序の7段(背景→負z→ブロック→フロート→インライン→z:0/auto→正z)と、負zが親背景の上・内容の下に入る件、(4) z-index:auto は非生成・z-index:0 は生成という差、(5) 通常フローのブロックは段3固定で position 等がないと z-index が効かない件、が頻出です。「大きな値にすれば必ず最前面」は上位の入れ子を無視した定番の誤りです。
まとめ
要素の重なりは z-index の数値ではなく、まずスタッキングコンテキストの入れ子で大枠が決まり、z-index の比較は同じコンテキストに直接属する要素間でのみ行われる辞書式構造です。コンテキストは root のほか、position+z-index の組、opacity 1未満、transform/filter/perspective、will-change、isolation: isolate、mix-blend-mode などが生成し、生成された部分木は1つの重なり単位になります。各コンテキスト内部のペイント順序は7段の決定木(背景→負z-index→ブロック→フロート→インライン→z:0/auto→正z-index)で固定され、負zは親背景の上・内容の下に入り、通常フローのブロックは段3に固定されて z-index を持てません。「z-index が効かない」は多くが祖先のどこかで別コンテキストが生まれ、親同士で既に順位が決まっているケースで、祖先を遡って生成要素を列挙すれば切り分けられます。重なりを設計するなら、閉じたい境界に isolation: isolate を明示し、不用意な opacity/transform を避けるのが定石です。土台は CSS、勝敗判定の辞書式構造は CSSカスケード・詳細度、描画段の全体像は レンダリングパイプライン詳説 と リフローとリペイントの分類 で押さえると、重なり順から画面表示までが一本につながります。
Web/フロントエンド Article
スタッキングコンテキストとz-indexの合成順序を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
CSS
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
スタッキングコンテキストはroot要素のほか、positionとz-index指定の組、opacity1未満、transform/filter、will-change、isolation:isolateなどが生成する。生成された瞬間にその部分木は独立した重なり単位になる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「CSS / スタッキングコンテキスト」に近いか確認する。
- 強みである「重なり順はz-index単独でなく、まず祖先のスタッキングコンテキストの入れ子で大枠が決まり、各コンテキスト内部だけでz-indexが比較される。親が下なら子をいくら上げても親同士の順位は覆らない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。