z-indexが効かない場合はtransformに気をつけよう!【Safari】

こんにちは、katori@xxbicotです。
この間、iPhoneとMacのSafariブラウザでcssのz-indexが効かない、なぜか指定した順番通りにならないという事象が発生しました。
どうやらtransformが怪しそう…ということで、調査して修正方法をまとめました。

Safariブラウザ以外にも有効な場合もあるようです。z-indexにお困りの方はご参考にどうぞ!

z-indexが効かない

今回起きた事象をシンプルなコードで再現してみました。

See the Pen z-index3Dver by BICOxxx (@BICOxxx) on CodePen.

サンプルでは赤と青の大きなdiv要素が並列の関係になっており、青divの子に緑のdiv要素があります。

  • A(赤色)position:absolute; z-index:2 を指定。(見やすくするためやや透過)
  • B(青色)position:absolute; z-index:1 を指定。
  • C(緑色)…青divの子要素。transform: translate3d(0,0,150px); を指定。

という感じ。

おおよそのブラウザでは下の図のように、上からA → C → Bの順で重なって表示されます。


ほとんどのブラウザの表示

しかし、Safariで先ほどのcodepenを見るとわかるかと思いますが、iOSとMacのSafariブラウザではC → A → Bの順で表示されてしまっています。


Safariブラウザの表示

Safariのバグが原因

要素の表示順に関して調べて見ると、2Dと3Dで異なる値を使用しているようです。
それぞれ以下のプロパティの値で制御しているとのこと。

表示順制御値

2Dレンダリング z-indexの値
3Dレンダリング 3D空間のZ値
transform: translate3d(0,0,X) または translateZ(X)

今回の事象は、この3D空間のZ値にSafariの不具合が関連して発生しているらしく、こちらに同事象についての詳しく書かれていました。

ちょっと理解できなかった部分があったので個人的な解釈が入りますが、

  • 基本的に2Dと3Dはそれぞれのグループ内で表示順が保持される
  • Safariでは3D要素や3Dアニメーションを行う際、元の2D(z-index)の表示順が失われてしまう
  • 具体的にはその要素のZ軸が別の2Dグループに入れられてしまう(?)

こういうことのようです。

これを解決するには、それぞれの表示順を保持してもらう必要があるので、3D要素を包む要素にtransformプロパティで3D空間のZ値を指定することで順番を指定してあげればいいとのこと。

親要素にtransform: translateZ()を指定することで解消

ためしてみます。

今回のサンプルではCの表示順がおかしくなっているので、親であるBに3DのZ値(ここでは transform:translate3d(0,0,0) )を追記。

#second {
    position: absolute;
    width: 100%;
    height:170px;
    background: #196ca7;
    text-align: right;
  
    z-index: 1;
    transform:translate3d(0,0,0); //ここを追記
}

これでさくっと修正できました!


Safari表示

おまけ)並列関係の場合

さらにSafariとそれ以外のブラウザの違いをわかりやすく並べてみました。
先ほどのサンプルから緑のC要素を消して、並列の関係のA、Bだけがあるとします。

スタイルはサンプルのまま、z-indexとtransformだけ変更します。

(1)z-indexのみ、transformなし

  • [A]z-index: 2;
  • [B]z-index: 1;

すべてのブラウザでAが前にでる。

(2)片方だけtranslateZを追加

  • [A]z-index: 2;
  • [B]z-index: 1; transform: translateZ(1px);

SafariではBが前にでる。Safari以外はAが前にでる。
([A]はtransform: translateZ(0)でも同様の表示)

(3)両方translateZを追加

  • [A]z-index: 2; transform: translateZ(1px);
  • [B]z-index: 1; transform: translateZ(1px);

すべてのブラウザでAが前にでる。

translateZの値が同じ場合はz-indexの表示順が反映されるようです。

まとめ

最初にも書きましたが、Safari以外のブラウザでもtransformの3DのZ値を調整したらz-indexが正常に効くようになったという事例をいくつか見かけました。
なのでz-indexが効かなくて困ったら、その要素の並列要素、もしくは親要素のtransformのZ値を確認してみましょう

以上、z-indexが効かない時の対処法でした!