iPhone Safariで100vhがはみ出す原因と直し方
100dvh・svhの使い分け

はじめに

全画面のログイン画面やランディングページで height: 100vh を使った。PCのChromeでは画面いっぱいにきれいに収まる。でもiPhoneのSafariで開くと、下のボタンがアドレスバーの裏に隠れたり、ページがわずかに縦スクロールしてしまったりする。

これは実装ミスではなく、iOS Safari特有のビューポートの仕様によるものです。原因と、今のCSSで使える対策をまとめます。

何が起きているか

iOS Safariはアドレスバーとツールバーが、スクロールに応じて表示されたり隠れたりします。ページの上部にいるときはアドレスバーが表示され、下にスクロールすると隠れて画面が広がります。

100vh はこの「アドレスバーが隠れた状態の最大の高さ」を基準に計算されます。つまりアドレスバーが表示されている初期状態では、100vh の要素は画面の表示領域より大きくなり、下端がアドレスバーの裏に隠れます。

.full-screen {
  height: 100vh; /* iPhone Safariでアドレスバー表示中は画面からはみ出す */
}

なぜPCのブラウザで気づかないのか

PCのブラウザにはアドレスバーの表示・非表示でビューポートの高さが変わる挙動がありません。Chrome DevToolsのモバイルシミュレーターも、固定された画面サイズをエミュレートするだけで、アドレスバーの開閉は再現しません。

この問題は実機のiPhone Safariで、かつページを開いた直後(アドレスバーが表示されている状態)でしか気づけません。下にスクロールした後だと再現しないこともあるため、見落とされやすい不具合です。

この挙動は記事内では再現できません

アドレスバーの表示状態によるビューポートの変化は、記事内の小さい枠では再現できません。お手元のiPhoneで以下のリンクを開いて確認してください。

崩れる例 100vh のまま開く iPhone Safariで下端のボタンがアドレスバーに隠れる状態を確認できます。 修正例 100dvh で開く アドレスバーの表示状態に追従する修正版を確認できます。

ページを開いた直後、アドレスバーが表示されている状態で下端のボタンを確認してください。「崩れるパターン」はボタンの下半分がアドレスバーの裏に隠れます。

修正方法:100dvhに置き換える

2023年以降に主要ブラウザがサポートした動的ビューポート単位(dynamic viewport units)を使うと、アドレスバーの開閉に追従して高さが自動調整されます。

.full-screen {
  height: 100dvh; /* アドレスバーの開閉に追従する */
}

dvh はSafari 15.4以降、Chrome 108以降で使用できます。このサイトが対象とする現行のモバイルブラウザではほぼ問題なく使えます。古いブラウザ向けのフォールバックが必要な場合は、vh を先に書いておくと未対応ブラウザでもレイアウトが崩れません。

.full-screen {
  height: 100vh; /* dvh未対応ブラウザ向けのフォールバック */
  height: 100dvh; /* 対応ブラウザではこちらが優先される */
}

3種類のビューポート単位の違い

dvh のほかに svh(small viewport height)と lvh(large viewport height)があります。用途によって使い分けます。

100lvh:アドレスバーが隠れた状態の高さ

100vh とほぼ同じ。常に最大の高さを基準にするため、アドレスバー表示中はみ出しが起きる。

100svh:アドレスバーが表示された状態の高さ

常に最小の高さを基準にする。はみ出さないが、アドレスバーが隠れたときに下に空白ができる。

100dvh:今の表示状態に追従する高さ

アドレスバーの開閉に応じて値が動的に変わる。全画面レイアウトには基本的にこれを使う。

「常にはみ出さない安全側で固定したい」場合は svh、「画面いっぱいに使いたい」場合は dvh を選びます。迷ったら dvh が無難です。

JSで高さを取得する場合の注意

CSSではなくJavaScriptで高さを計算している場合は、window.innerHeight ではなく visualViewport.height を使うと、アドレスバーの開閉に追従した値が取得できます。

/* window.innerHeight は変化に追従しないことがある */
const height = window.visualViewport
  ? window.visualViewport.height
  : window.innerHeight;

visualViewport はリサイズイベントも発火するため、アドレスバーの開閉に合わせて再計算する処理も組みやすくなります。

window.visualViewport.addEventListener('resize', () => {
  // アドレスバーの開閉などでビューポートが変化したときに再計算する
});

まとめ

iPhone SafariのアドレスバーはCSSのvhに影響しません。100vhはアドレスバーが隠れた最大の高さを基準にするため、表示中ははみ出します。

全画面レイアウトは100dvhに置き換えるのが基本対策です。常に安全側で固定したいときはsvh、JSで高さを取得する場合はvisualViewport.heightを使います。PCのブラウザやシミュレーターでは再現しないため、全画面レイアウトを組んだら実機のiPhoneで確認することを手順に組み込んでおくと安心です。

関連するUI事例

全画面レイアウトでこの問題が起きやすい代表的なUI事例です。