100vhが嘘をつくとき:モバイルビューポート問題の解決法
フルハイトのヒーローセクションを構築します。Chrome DevToolsでは完璧に見えます。しかし、スマートフォンで開くと下部のコンテンツが切れています — 配置に時間をかけたCTAボタンがブラウザのアドレスバーの裏に隠れています。聞き覚えがありませんか?
これはもはやSafariだけの癖ではありません。ビューポート単位が基本的にどのように定義されているかの結果であり、モバイルのChrome、Firefox、Samsung Internetにも影響します。
重要なポイント
- モバイルでの
100vhは、ラージビューポート(ツールバーが格納された状態)に対して計算されるため、初期読み込み時にユーザーが実際に見る画面よりも高くなります。 - ツールバーが表示されているときに可視画面に収まる安定したフルハイトレイアウトには
100svhを使用してください。 - ブラウザUIの変化に応じてレイアウトを明示的にリサイズしたい場合にのみ
100dvhを使用し、リフローのコストを受け入れてください。 - エッジ・トゥ・エッジディスプレイでは、ビューポート単位と
env(safe-area-inset-bottom)を組み合わせて、コンテンツがシステムUIの裏に配置されるのを防いでください。
なぜモバイルで100vhが嘘をつくのか
根本原因は、2つの異なるビューポート概念に起因します:
- レイアウトビューポート: ブラウザが
vhを含むCSSの長さを計算するために使用するもの - ビジュアルビューポート: ユーザーが実際に画面上で見るもの
モバイルブラウザは、スクロール中にアドレスバーとナビゲーションツールバーが格納されても、意図的にレイアウトビューポートを固定したままにします。これは、100vhが展開されたツールバー状態 — ラージビューポート — に対して計算され、その値はページが最初に読み込まれたときの可視画面よりも大きくなることを意味します。
これはバグではありません。 CSS仕様では
vhをlvh(ラージビューポート高さ)と同等と定義しています。ブラウザは正しく動作しています。あなたのレイアウトがそれを考慮していないだけです。
これがDevToolsのモバイルエミュレーションが正常に見える理由です:動的なブラウザUIをシミュレートしていません。シミュレーターではアドレスバーは決して格納されません。
実際の影響
- ヒーローセクションが初期読み込み時に56〜80pxオーバーフローする
- 固定フッターがナビゲーションバーに隠される
- フルスクリーンモーダルの下部でコンテンツが切れる
- (
dvhを使用している場合)スクロール中にツールバーが格納されるとレイアウトがジャンプする
Discover how at OpenReplay.com.
モダンな解決策: svh、dvh、lvh
CSSは現在、異なるツールバー状態に対応する3つの明示的なビューポート高さ単位を提供しています:
| 単位 | 表すもの | 最適な用途 |
|---|---|---|
lvh | ラージビューポート(ツールバー格納時) | 現在のvhの動作と同じ |
svh | スモールビューポート(ツールバー表示時) | 安定したレイアウト、常に表示されるコンテンツ |
dvh | ダイナミックビューポート(現在の状態) | 適応的なレイアウト、ただしリフローが発生 |
ほとんどのフルハイトセクションには: svhを使用
.hero {
height: 100vh; /* 古いブラウザ用のフォールバック */
height: 100svh; /* 読み込み時に可視画面内に収まる */
}
100svhは要素を最小の可能なビューポートにサイズ調整します — つまり、ツールバーが完全に表示されているときでも収まります。オーバーフローなし、クリッピングなし。
動的な調整が必要な場合: dvhを慎重に使用
.full-height {
height: 100vh; /* フォールバック */
height: 100dvh; /* ツールバーの格納に応じてリサイズ */
}
警告: dvhはスクロール中にブラウザUIが変化すると再計算されます。これにより、目に見えるレイアウトシフトと再描画が発生する可能性があります。ほとんどのヒーローセクションやモーダルには適切なデフォルトではありません — ツールバーの状態に応じて要素を明示的にリサイズしたい場合にのみ使用してください。
ブラウザサポート
svh、dvh、lvhはChrome 108+、Safari 15.4+、Firefox 101+でサポートされています。合わせて世界中のユーザーの90%以上をカバーしています。古いブラウザについては、100vhのフォールバックが残りを処理します。
知っておくべきエッジケース
セーフエリアインセット: エッジ・トゥ・エッジディスプレイ(iPhoneのノッチ/Dynamic Island)では、ビューポート単位とenv(safe-area-inset-bottom)を組み合わせて、コンテンツがシステムUIの裏に配置されるのを避けます:
.hero {
height: 100svh;
padding-bottom: env(safe-area-inset-bottom);
}
オンスクリーンキーボード: 仮想キーボードは主にビジュアルビューポートに影響し、レイアウトビューポートのサイズ設定との相互作用はブラウザやWebViewによって異なる場合があります。これはツールバー駆動のビューポート単位とは別のものであり、JavaScriptの処理やVirtualKeyboard APIをプログレッシブエンハンスメントとして必要とする場合があります。
WebViewと組み込みブラウザ: アプリ内ブラウザ(Instagram、LinkedIn)は、しばしば非標準のビューポート動作をします。システムブラウザだけでなく、実際のデバイスでテストしてください。
まとめ
モバイルのフルハイトレイアウトのデフォルトとして100vhに手を伸ばすのをやめましょう。読み込み時に可視画面に収まる必要がある安定したセクションには100svhを使用してください。動的なリサイズが意図的である場合にのみ100dvhを使用してください。新しいビューポート単位ファミリーが存在するのは、まさに古い動作が常に妥協であったためです — 今、あなたが実際に望むものについて明示的になるためのツールがあります。
よくある質問
すべての場所ではありません。100svhは最小のビューポート高さを提供し、100vhよりも短くなります。ツールバーが格納された後にのみ画面を埋める必要がある要素には、100lvhまたは100dvhがより適切な場合があります。ツールバーが表示されている初期ページ読み込み時に完全に表示される必要があるコンテンツには、特に100svhを使用してください。
影響する可能性があります。dvhはスクロール中にブラウザツールバーが格納または展開されるたびに再計算されるため、レイアウトの再計算と再描画をトリガーします。静的なヒーローセクションやモーダルの場合、このオーバーヘッドは不要です。現在の可視ビューポートをリアルタイムで追跡するために高さを意図的に変更したい要素にのみdvhを使用してください。
動作は異なります。標準的なiframeでは、ビューポート単位は親ページではなく、iframe自身のビューポートを参照します。InstagramやFacebookなどのアプリ内ブラウザで使用されるWebViewでは、ツールバーの動作が非標準であり、ビューポート単位の計算が予測不可能になる場合があります。ユーザーが遭遇する特定の組み込みコンテキストで、常に実際のデバイスでテストしてください。
svh、dvh、lvh単位は仮想キーボードの影響を受けません。キーボードはビジュアルビューポートを縮小しますが、これらの単位が参照するレイアウトビューポートは縮小しません。キーボード関連のレイアウトシフトを処理するには、VirtualKeyboard APIを調べてください。これにより、キーボードの可視性の変化にレイアウトがどのように応答するかをプログラムで制御できます。
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..