AQ Tech Blog

Safariでのみヘッダーのz-indexが効かなかったときの対処法ログ

作成者: saki.oishi|2024年11月29日

はじめに

デジタルエンジニアリング部グロースエンジニアリング課の大石です。
CMSを使用した案件でHTML,CSS,JavaScriptを使ってサイト制作を行っていました。
その際に発生して困ったことと、解決までの道のりをまとめていきたいと思います。

iPhoneのSafariでのみ重なり表示がおかしい問題

困ったこと

iPhoneのSafariでページを確認したときのみ、ハンバーガーメニュー内のz-indexがおかしくなった
(CMS内でHTML,CSS,JavaScriptを使用してコーディング)

iPhoneのSafariでのみヘッダーの表示がおかしい

ヘッダーで使ったコードは以下の通り

HTML

サンプルコードA(折りたたみ)
<header class="header">
<div class="header-container">
<h1 class="header-logo"></h1>
<div class="header-menu">
<button type="button" class="navbar-toggle">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="header-nav">
<ul class="menu-list">
<li class="menu-item"><a href="#">項目</a></li>
<li class="menu-item"><a href="#">項目</a></li>
<li class="menu-item"><a href="#">項目</a></li>
</ul>
</div>
</div>
</div>
</header>

CSS

サンプルコードB(折りたたみ)
header {
position: fixed;
z-index: 10;
top: 0;
right: 0;
left: 0;
width: 100%;
padding: 0px 24px 12px;
transition-duration: 300ms;
background-color:#FFF;
}

.header-container {
display: flex;
align-items: center;
justify-content: space-between;
}

.header-logo {
width: 130px;
height: auto;
background-color: #841d20;
margin: 0;
}

.navbar-toggle {
display: none;
position: fixed;
width: 50px;
height: 50px;
background: transparent;
border: none;
cursor: pointer;
order: 2;
z-index: 100;
}

.header-nav {
display: flex;
justify-content: space-between;
}

.menu-list {
display: flex;
}

.menu-item {
text-decoration: none;
display: inline-block;
color: #FFF;
padding: 10px 40px 10px 30px;
text-align: center;
outline: none;
}

.menu-item {
color: #0a100d;
}

.menu-item a {
position: relative;
display: block;
padding: 15px 0;
font-size: 16px;
font-weight: normal;
color: #0a100d;
text-decoration: none;
transition: .2s;
box-sizing: border-box;
}

@media screen and (max-width: 959px) {
.header-nav {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
z-index: 99;
padding-top: 80px;
overflow-y: auto;
transition: none;
-webkit-overflow-scrolling: touch;
}

.header-nav.active {
display: block;
}

.menu-list {
display: block;
text-align: center;
}

.menu-item a {
color: #FFF;
}

.header-menu{
width: 50px;
height: auto;
}

.navbar-toggle {
display: inline-block;
position: relative;
top:2px;
}

.navbar-toggle:hover{
background-color:transparent;
border:none;
}

.navbar-toggle:visited {
background-color:transparent;
border:none;
}

.navbar-toggle span {
display: inline-block;
transition: all .4s;
position: absolute;
left: 14px;
height: 3px;
width: 30%;
background: #0A100D;
border-radius: 2px;
}

.navbar-toggle span:nth-of-type(1) {
top: 15px;
}

.navbar-toggle span:nth-of-type(2) {
top: 23px;
}

.navbar-toggle span:nth-of-type(3) {
top: 31px;
}

.navbar-toggle.active span:nth-of-type(1) {
top: 23px;
transform: rotate(45deg);
background-color: #FFF;
}

.navbar-toggle.active span:nth-of-type(2) {
opacity: 0;
}

.navbar-toggle.active span:nth-of-type(3) {
top: 23px;
transform: rotate(-45deg);
background-color: #FFF;
}
}

オーソドックスなもので、特殊なことはしていないように見えます。
そしてページが出来上がり、いざ実機テスト!
が、iPhoneのSafariでのみ.header-menu部分だけが見えていません。
ヘッダー部分には背景色が見えているものの、肝心の内容部分は要素に覆われ見えていない状態。
z-indexは99に設定しているので、本来であれば画面の最上段に表示されるはず。
しかし実際はiOSのバージョンに関わらず表示されないという有様。
念の為確認すると、タップは出来る模様。
完全に下へ潜りこんでしまっているようです。
一体何が起こっているのかわからないながら、一つずつ調べて対応してみることに。
まだこの段階ではまさか二段階の問題が潜んでいるとは露知らず、調べれば解決するだろうという考えでいました。

レンダリングの問題

早速この状況を解決すべくインターネットを活用して調べてみると、「レンダリング」という単語が出てきます。
今回のCSSではtransformを使用していませんので、正直関係ないのでは?と思いながら検索すると、かなりの量が出てくる「Safariでz-indexが効かない問題」に関する記事。やはり多くの人が悩んでいる問題なのかと実感しつつ、複数の記事を読んでみることに。
そして調べた結果、以下のことがわかりました。

2Dレンダリング(z-index)と3Dレンダリング(transform)はそれぞれのグループ内で表示順が保持されるようです。これによって、要素ごとに正しい順番で表示されます。
しかし、Safariの場合はやや仕様が異なるらしく、3D要素や3Dアニメーションを行う際、元の2Dレンダリングの表示順が失われてしまい、制御順に異常が生じるとのことでした。具体的にはその要素のZ軸が別の2Dグループに入れられてしまうらしいのですが、この辺りに関してはよくわからないというのが正直なところ。
これを解決するには、それぞれの表示順を保持してもらう必要があるので、3D要素を包む要素にtransformプロパティで3D空間のZ値を指定することで順番を指定してあげればいいとのこと。
つまるところ、「2D(z-index)だけで制御をしようとするとおかしくなってしまうことがあるよ、だから3Dの情報も持たせてあげてね」ということらしいです。PC側のSafariでスマートフォン版の表示を確認しましたが正常に動作していたため、今回はiPhoneに限定した制御異常が起こっているのでは?と考えました。

ここまでわかったところで解決策として考えたのは以下の2点

①コードの構造を変える
②3Dレンダリングの値を入れる

①コードの構造を変える

極論ですが、z-indexの低いdivの中にz-indexの高いdivを入れても、そのdivよりも上には表示されません。
だったら物理的にそのdivから要素を外してz-index:9999とかを当てはめれば万事解決ではないかと考えました。しかし、ソースを考えると厳しかったので早々に諦めました。CMSのグローバルメニュー機能を使っているというのもあり、なるべくHTMLファイルをいじるのはな……と考えた結果です。

②3Dレンダリングの値を入れる

①の対応策がダメだった以上、残された選択肢は必然的に3Dレンダリングの値を入れるというものになります。
今回問題となっている要素の親要素である部分にtransformを付与すれば上手くいくかもしれないと思い、早速試してみます。どちらも位置に関するものであるz-indexとtransformを併用するということにほんの少しだけ違和感を覚えますが、Safari専用コードだと思えば納得です。Safari側のバグでうまく認識してくれないなら、コードを足してSafariでも問題なく表示できるようにしようね、という対応する端末が多いと生じる問題への対応策になります。
そんなわけで表示されていない要素の親要素にtransform:translateZ(1px);を追記してみました。

.header-nav {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
z-index: 99;
padding-top: 80px;
overflow-y: auto;
transition: none;
-webkit-overflow-scrolling: touch;
transform: translateZ(1px); ←これを追記
}

divに対し、Z軸を1px上げることで最上面に浮かばせるという仕組みのようです。
私はこれでは変化なしでした。
多くの記事、多くの先達がこれで何とかなったよ~というお墨付きの方法がまさかの効果なしという結果にがっかりしながら、次の策を考えます。

レンダリングをもうすこししっかり考える

少し乱暴な手法ですが、「ヘッダー側を上げるのではなく、下に潜り込んでしまっている時に出ている要素を下げればいいのでは?」と考えました。
つまりはメイン要素にレンダリングに関する情報を持たせる、ということですね。
通常であればz-indexは0であると定義づけられているはずなので記述する必要はないものの、今回レンダリングで異常が生じているのなら改めて定義してあげるべきでしょう。
そんなわけで、メイン要素にもCSSを書き足していきます。
今回対応が必要なのはiPhoneのSafariのみ、と限定されているので、「この要素のZ軸は0だよ」というコードを追記します。

main {
transform: translateZ(0,0,0); ←これを追記
}

結果はこれが原因ではなかったらしく、期待した結果にならなかったのでふりだしに戻ります。

新たな問題

ヘッダーが少しずれている

ここまで真剣にヘッダーと向き合ってきました。
真剣に向き合った結果、実機の確認でさらに別の問題を見つけることになります。
ハンバーガーメニューを開いてスクロールするとなんだかずれている。
動きがかくついているせいではなく、物理的にずれている。
同様の状況に陥っている方の投稿を拝見した時に「これかも!」と思い当たり、詳しく読んでいきます。

スムーズなスクロールを可能にする「-webkit-overflow-scrolling: touch;」が最上面に表示したいdivよりも上に入っているとz-indexが効かないことを突き止め、「html」部分に記述していた「-webkit-overflow-scrolling: touch;」を削除するとz-indexが正常に動きました。
iPhone Safariでz-indexが効かず重なり順が正常に動かない場合の対処法より引用

天啓を得た気分です。
ヘッダーは基本的に一番上に記述される要素になるので確かに理にかなっています。z-indexはおかしいしなんだかずれているという状況で徐々に手詰まりになっていたためすぐに実装。

.header-nav {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
z-index: 99;
padding-top: 80px;
overflow-y: auto;
transition: none;
-webkit-overflow-scrolling: touch; ←これを削除
}

結果は「ずれは直ったものの、依然としてz-indexは治っていない」というものに。

対処方法

positionの罠

最終的に解決した方法です。
先ほどのスクロールするとヘッダーの中身がずれてしまうという記事を確認していると、似たような投稿を発見しました。

表示されている状態で三をクリックしメニューを開き、そのままの状態でスクロールすると、スクロールの速度に関係なく下にずれてしまいます。
Safariで見るとハンバーガーメニューがずれてしまうより引用

その解決方法に、position:fixedが悪さをしているんじゃないか?ということが書かれています。

position:fixedを指定した要素だけ別枠扱いになってる
アイフォンSEは「position:fixed」した要素のz-indexが効かないより引用

別枠扱い。 唐突に出てきた全く別の仕様に愕然としました。
2D、3D、positionで考える必要があるとなると、これまでの話が少し変わってきます。慌てて数個記事を確認し、その中でわかったことは以下の通り。

position:fixedの付いた要素の中にposition:fixedの要素があるとなんだかおかしくなる。

これです。
私の場合はこれが悪さをしていました。
最初に出したHTMLを確認します。

<div class="header-menu">
<button type="button" class="navbar-toggle">←ここがposition:fixed
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="header-nav">←ここがposition:fixed
<ul class="menu-list">
<li class="menu-item"><a href="#">項目</a></li>
<li class="menu-item"><a href="#">項目</a></li>
<li class="menu-item"><a href="#">項目</a></li>
</ul>
</div>
</div>

該当CSS

.navbar-toggle {
display: none;
position: fixed;←ここがfixed
width: 50px;
height: 50px;
background: transparent;
border: none;
cursor: pointer;
order: 2;
z-index: 100;
}

.header-nav {
display: none;
position: fixed;←ここもfixed
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
z-index: 99;
padding-top: 80px;
overflow-y: auto;
transition: none;
-webkit-overflow-scrolling: touch;
}

正確には同一のdivにposition:fixedが2つあるという状況がよろしくないようです。 .header-menu内には.navbar-toggle と .header-nav の二つが子要素として存在しており、どちらもposition:fixedだったから意図しない表示になっていたのでは……?という仮説を立てました。
CMSの都合上、コードは変えたくないので.navbar-toggleのpositionをrelativeに変更し、位置を調整します。

.navbar-toggle {
display: none;
position: relative;←ここを修正
width: 50px;
height: 50px;
background: transparent;
border: none;
cursor: pointer;
order: 2;
z-index: 100;
}

これでヘッダーがずれることなく、iPhoneのSafariでも表示することができるようになりました!

結論

・同一のdiv内にposition:fixedが二つ以上存在している場合、iPhoneでz-indexがおかしくなることがある
・Safariでは2D(z-index)のみで制御しようとすると表示順がおかしくなる場合があるので、transform3Dを追加するといい

今回、まさかの二段構えで問題ということに気づかず、予想外に時間を割くことになってしまいました。デバイスの仕様は奥が深いですね……。 そもそもとしてコードを書き換えられれば一番楽に対応ができたのですが、今回のようにそうはいかない場面も多々あると思います。
しっかり覚えておこうと思います。
iPhoneがなくなるということは考えづらいので、次にコーディングするときはこの仕様を把握しておくことで開発がスムーズになりそうです。
transformも結果としては効かなかったものの、他要素では応用が利きそうな印象。
今回初めて執筆をしましたが、今後もなにか気づきがあればブログという形にしたいです。