CSSのz-indexが効かないときの原因と対処法【CSS】

CSS

こんにちは!コーダーのゆうしです。

CSSで z-index を指定したのに「なぜか重なり順が変わらない」「思った通りに前面に来ない」という経験はありませんか?実はこれ、コーディング中によく詰まるポイントのひとつです。原因のほとんどは スタッキングコンテキスト(Stacking Context) という概念が絡んでいます。この記事では z-index が効かない原因と対処法をわかりやすく解説します。

z-indexが効く条件

まず大前提として、z-indexposition プロパティが指定されている要素にしか効きません。

/* NG:positionがないとz-indexは効かない */
.box {
  z-index: 100;
}

/* OK:positionと一緒に使う */
.box {
  position: relative;
  z-index: 100;
}

position: static(デフォルト)の要素には z-index が効かないので、まずここを確認してみてください。ただ position を指定しているのに効かない場合は、スタッキングコンテキストが原因の可能性が高いです。

スタッキングコンテキストとは?

スタッキングコンテキストとは「z-indexの比較が行われる範囲」のことです。同じスタッキングコンテキスト内の要素同士だけがz-indexで比較されます。

わかりやすく例えると「同じ部屋の中だけで前後関係が決まる」イメージです。別の部屋(スタッキングコンテキスト)に属している要素とは直接比較できません。

z-indexが効かない原因

① 親要素がスタッキングコンテキストを作っている

一番よくある原因です。親要素がスタッキングコンテキストを作ると、子要素のz-indexはその中でしか比較されません。

<div class="parent-a">  <!-- z-index: 1 -->
  <div class="child-a">  <!-- z-index: 9999 -->
</div>

<div class="parent-b">  <!-- z-index: 2 -->
  <div class="child-b">  <!-- z-index: 1 -->
</div>
.parent-a {
  position: relative;
  z-index: 1;
}

.child-a {
  position: relative;
  z-index: 9999; /* 親がz-index:1なので9999でも意味がない */
}

.parent-b {
  position: relative;
  z-index: 2;
}

.child-b {
  position: relative;
  z-index: 1;
}

この場合 .child-az-index: 9999 を指定していても .child-b より後ろに表示されます。なぜなら .child-a の親(.parent-a)のz-indexが 1 で、.child-b の親(.parent-b)のz-indexが 2 だからです。子要素同士ではなく親要素同士で比較されています。

対処法: 親要素のz-indexを調整するか、スタッキングコンテキストを作らないようにする。

② スタッキングコンテキストを作るプロパティを使っている

以下のCSSプロパティはスタッキングコンテキストを生成します。意図せずスタッキングコンテキストが作られていることがあるので注意してください。

プロパティ条件
positionrelative / absolute / fixed / sticky かつ z-indexauto 以外
opacity1 未満の値
transformnone 以外の値
filternone 以外の値
will-changetransform / opacity などを指定
isolationisolate を指定
mix-blend-modenormal 以外の値

特に transformopacity は気づかずに使っていることが多いです。アニメーションのためにこれらを親要素に指定していると、意図せずスタッキングコンテキストが生まれてz-indexが効かなくなります。

/* これだけでスタッキングコンテキストが生成される */
.parent {
  transform: translateX(0); /* noneじゃないのでコンテキスト生成 */
  opacity: 0.99;            /* 1未満なのでコンテキスト生成 */
}

対処法: 不要な transformopacity を親要素から削除するか、z-indexの構成を見直す。

③ z-indexの数値が大きければ前面に来ると思っている

同じスタッキングコンテキスト内であれば数値が大きいほど前面に来ますが、別のスタッキングコンテキストに属している要素とは数値で比較できません

/* 別のスタッキングコンテキストにいる要素とは比較できない */
.modal {
  z-index: 99999; /* 数値を大きくしても意味がない場合がある */
}

対処法: z-indexで比較したい要素が同じスタッキングコンテキストに属しているか確認する。

実際の対処の手順

z-indexが効かないと感じたら以下の順番で確認してみてください。

① positionが指定されているか確認する

/* z-indexを使う要素にpositionがあるか確認 */
.target {
  position: relative; /* これがないとz-indexは効かない */
  z-index: 10;
}

② 開発者ツールで親要素を確認する

Chromeの開発者ツールでz-indexが効かない要素の親要素を確認します。親要素に transformopacity が指定されていないかチェックしてください。

③ 親要素のスタッキングコンテキストを確認する

親要素に z-index が指定されていないか・opacity1 未満になっていないかを確認します。

④ isolation: isolateを使う

意図的にスタッキングコンテキストを分離したい場合は isolation: isolate が使えます。

.wrapper {
  isolation: isolate; /* 新しいスタッキングコンテキストを作る */
}

モーダルやドロップダウンなど、特定のコンポーネントだけで重なり順を管理したいときに便利です。

z-indexの管理をシンプルにする

プロジェクト全体でz-indexが増えてくると管理が大変になります。CSS変数でz-indexを一元管理しておくと把握しやすくなります。

:root {
  --z-base:    1;
  --z-header:  100;
  --z-modal:   200;
  --z-toast:   300;
  --z-tooltip: 400;
}

.header {
  position: fixed;
  z-index: var(--z-header);
}

.modal {
  position: fixed;
  z-index: var(--z-modal);
}

どのコンポーネントがどのz-indexを使っているか一目でわかり、数値の衝突も防ぎやすくなります。

躓きやすいポイント

transformを削除したら別の問題が出る

アニメーションのために transform を使っている場合、削除すると動きが壊れることがあります。その場合は transform: translateZ(0) を試してみてください。GPUレイヤーを分離しつつ見た目への影響を最小限にできることがあります。

z-indexを大きくし続けてしまう

z-index: 9999 で解決しなくて z-index: 99999 に増やす…というのを繰り返していると、どこかで根本的に破綻します。z-indexが効かないと感じたらまず原因を特定してから対処するほうが長期的にコードがきれいに保てます。

headerやmodalが特定のセクションの裏に入る

セクションに transformfilter が指定されていてスタッキングコンテキストが生まれているケースです。特にCSSアニメーションを使っているセクションの近くでよく起こります。該当セクションの親要素を確認してみてください。

まとめ

z-index が効かない原因のほとんどはスタッキングコンテキストの問題です。まず position が指定されているか確認して、次に親要素に transformopacity が指定されていないかを確認してください。CSS変数でz-indexを一元管理しておくと、プロジェクトが大きくなっても破綻しにくくなります。

ご不明な点やコーディングのご依頼はお問い合わせからお気軽にどうぞ。

タイトルとURLをコピーしました