CSS講座 / z-indexとスタッキングコンテキスト:重なり順を調べる

z-indexは、関係するスタッキングコンテキストの中で重なり順を指定します。子の数値を大きくしても、親のまとまりが他の要素より後ろなら前へ出られず、overflowによる切り取りも解除できません。

位置と重なり順を分ける

z-indexの差を見るには、二つの要素が画面上で実際に重なっている必要があります。左右に離れていれば数値を変えても違いは見えません。まず開発者ツールで長方形の位置と重なりを確認します。

positionは位置の基準と通常の配置への参加を決め、z-indexは重なった前後を扱います。relativeやabsoluteなどで位置指定された要素のほか、Flex・Gridアイテムにはstaticでも適用できます。普通のブロックへ数値だけを付けても、適用条件を満たさない場合があります。MDNのz-index、positionの基礎を参照してください。

親と子を一つのまとまりで比べる

左の親がz-index: 1、右のカードが2なら、左の中のバッジが9999でも右のカードと直接競いません。外側では左のまとまり全体が後ろです。

Left parent context: z-index 1
└ Child badge: z-index 9999
Right card context: z-index 2

Outside, compare parent 1 and card 2.

左の親を3へ上げると、そのまとまりが右の2より前になります。バッジは9999のままでも、重なり部分が見えるようになります。親と子の数字を足して10000といったページ共通の値を作る仕組みではありません。MDNのスタッキングコンテキストで親子関係を確認できます。

新しいコンテキストができる条件

代表的な条件 注意点
relative / absoluteとauto以外のz-index 0も数値であり、autoとは違います。
fixed / sticky z-indexがautoでもコンテキストを作ります。
Flex / Gridアイテムとauto以外のz-index positionがなくても作れます。
1未満のopacity、none以外のtransform 見た目だけでなく前後のグループにも影響します。
isolation: isolate 意図的に独立したまとまりを作ります。

これはよく使う条件の抜粋です。relativeだけでz-indexがautoの場合と、relativeにz-index: 0を組み合わせる場合は区別します。移動量0のtransformもnoneとは異なります。

CSSの描画順には負のレベルや通常の配置なども含まれます。すべてのautoを数値0とみなしてページ全体を計算しないでください。実習は正の数値と明示した親の条件に絞っています。

親の数値だけを変えて比較する

次をstacking-context.htmlとして保存します。バッジは上から80〜144px、右のカードは110〜230pxで、縦に34px重なります。横位置を親幅の割合にし、狭い画面でも比較できるようにしています。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>stacking-context.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.scene { position: relative; width: min(100%, 520px); height: 300px; background: #f1f4f7; }
 .left { position: absolute; z-index: 1; left: 0; top: 20px; width: 60%; height: 180px;
 padding: 16px; background: #ffe3b5; }
 .badge { position: absolute; z-index: 9999; left: 70%; top: 60px; width: 50%; height: 64px;
 padding: 8px; background: #f5a7cc; }
 .right { position: absolute; z-index: 2; left: 50%; top: 110px; width: 45%; height: 120px;
 padding: 12px; background: #9cd4f2; }
 .raised .left { z-index: 3; }
  </style>
</head>
<body>
<h1>Child and parent z-index values</h1><h2>Parent: 1; right card: 2</h2><div class="scene original"><div class="left">Left parent<div class="badge">Child badge<br>9999</div></div><div class="right">Right card<br>z-index: 2</div></div><h2>Parent: 3; right card: 2</h2><div class="scene raised"><div class="left">Left parent<div class="badge">Child badge<br>9999</div></div><div class="right">Right card<br>z-index: 2</div></div>
</body>
</html>

最初の領域ではバッジが青いカードに隠れ、次の領域では前に出ます。二つの違いは左の親の1と3です。図はこのグループ関係を示しています。

親1の子9999はカード2の後ろ、親3へ変更すると子も前へ出る概念図

  1. 最初の.badgeだけを99999へ変え、隠れる部分が変わらないことを確認して9999へ戻します。
  2. .original .leftにz-index: 3を追加します。座標を変えずにバッジが上へ出るか見て、追加を削除します。
  3. 元の最初の.leftをautoへ変えます。他に生成条件のないこの例では親の独立したコンテキストがなくなり、バッジが外のカードより前へ出ます。1へ戻します。
  4. 画面幅を変えても実際に重なるか確認します。離れた状態の数値比較は避けます。

overflowの切り取りは別に調べる

次をstacking-clip.htmlとして保存します。親の外へ出るバッジをoverflow: hiddenで切り取る例です。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>stacking-clip.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.frame { position: relative; width: min(100%, 260px); height: 100px; overflow: hidden;
 border: 2px solid #789; background: #e8f5de; }
 .popup { position: absolute; z-index: 9999; left: 80%; top: 24px; width: 90px; height: 60px;
 padding: 8px; background: #f5a7cc; }
  </style>
</head>
<body>
<h1>A box that stays clipped at a higher z-index</h1><div class="frame">Parent boundary<div class="popup">Badge<br>9999</div></div><p>The right side of the badge extends beyond its parent's boundary.</p>
</body>
</html>

z-indexを99999へ変えても、親の境界の外は見えません。9999へ戻し、.frameのoverflowだけをvisibleへ変えます。座標と数値が同じでも外側が見えることを確認してhiddenへ戻します。

実際の画面ではoverflowがスクロールや角丸のために必要な場合もあります。すべての祖先を一度に変えず、切り取りが意図どおりかを判断します。外側へ出す必要があるポップアップなら、切り取る祖先の外へ構造を移すなどの設計も検討します。はみ出しと表示範囲を参照してください。

原因を絞る確認手順

  1. 要素が存在し、display・visibility・opacityで隠れていないかを確認します。
  2. 二つの要素の座標と重なる範囲を確かめます。
  3. 祖先をたどり、コンテキストを作る指定と、比較される共通の親を探します。
  4. 変更する対象が子なのか親のまとまりなのかを判断し、影響する兄弟も確認します。
  5. 前後を直しても見えなければ、overflowやclipなどの切り取りを調べます。一時的な変更は戻します。

通常の要素の数字を増やすだけで、トップレイヤーに表示されたdialogなど別の層を越えられるわけでもありません。小さい数値でも、比較されるグループが分かれば意図した順番を構成できます。