スマートフォンのブラウザで全画面表示(ファーストビューやモーダルなど)を作ろうとしてheight: 100vh;を指定した際、「下部がアドレスバーに隠れて見切れる」「画面いっぱいに指定したはずなのに高さが合わない」「予期せぬスクロールバーが出る」といった現象に悩まされた経験はないでしょうか。

この問題は、CSSに新しく追加されたビューポート単位(svh / dvh / lvh)を使うことで、多くのケースにおいてCSSだけで直感的かつシンプルに解決できます。

この記事では、スマホで100vhがずれる原因から、各新単位の違い・使い分け、heightとmin-heightの選択、それでも崩れるときの切り分けポイントまでを解説します。

スマホで100vhを使うと高さがおかしくなる原因

スマホで100vhが期待通りに動かない理由は、モバイルブラウザ特有の「URLバー(アドレスバー)や下部ツールバーの伸縮仕様」とビューポート単位の基準にあります。

スマホではアドレスバーやツールバーで表示領域の高さが変わる

SafariやChromeなどのスマホブラウザでは、ページをスクロールするとアドレスバーが縮小したり、下部のナビゲーションツールバーが画面外へ隠れたりして、Webページが見えている実際の領域(可視領域)が動的に変化します。

【ブラウザの表示状態】
┌─────────────────────────┐  ┌─────────────────────────┐
│ アドレスバー (表示中)   │  │ アドレスバー (縮小)     │
├─────────────────────────┤  ├─────────────────────────┤
│                         │  │                         │
│                         │  │                         │
│ 可視領域                │  │ 可視領域                │
│ (小さくなる)            │  │ (広がる)                │
│                         │  │                         │
├─────────────────────────┤  │                         │
│ 下部ツールバー (表示中) │  │                         │
└─────────────────────────┘  └─────────────────────────┘
  [初期表示 / 上部スクロール]     [下方向スクロール中]

100vhは現在見えている画面の高さとは限らない

現在の仕様では、vhはブラウザUIが格納されたときの大きい表示領域(large viewport)を基準とし、後述するlvhと同等です。

そのため、アドレスバーやツールバーが表示されている状態では、100vhが実際に見えている領域より大きくなり、要素の下部がツールバーの後ろに潜り込んで見切れてしまう現象が起こります。

まずは100svhまたは100dvhへ変更して確認する

この問題を解決するために策定されたのが、新しいビューポート単位です。まずは目的の挙動に合わせて100svhまたは100dvhに変更してみるのが最短の解決策です。

高さを安定させたい場合は100svh

ブラウザUIが最大限表示されている「小さい表示領域(small viewport)」を基準とする単位です。

.hero {
  height: 100svh;
}

ブラウザUIが表示されている状態でも画面内に収まりやすく、スクロール中に基準となる高さが変化しないため、要素サイズが急に伸縮するガタつきを防ぐことができます。

表示領域の変化に追従させたい場合は100dvh

アドレスバーなどの表示状態に応じて、現在のviewport高さへ追従する単位です。

.modal {
  height: 100dvh;
}

UIの表示・非表示に合わせてサイズが調整されるため、現在の表示領域に合わせやすいのが特徴です。

svh・lvh・dvh・vhの違い

新しいビューポート単位は、それぞれ参照する「ブラウザUIの状態」が異なります。

単位正式名称基準となる画面の高さ特徴
svhSmall Viewport Heightツールバーが表示されている状態(Small viewport)スクロールしても高さが変化せず安定する
lvhLarge Viewport Heightツールバーが隠れている状態(Large viewport)画面が最も広い状態を基準にする
dvhDynamic Viewport Height現在のツールバーの状態に合わせて可変(Dynamic viewport)ブラウザUIの状態に応じて変化する
vhViewport HeightLarge viewport現在の仕様ではlvhと同等

100svhと100dvhはどちらを使えばよい?

実務では、「画面のガタつきを避けたいか」「現在の表示領域に合わせたいか」で使い分けます。

ファーストビューでは100svhが使いやすいケース

トップページのファーストビュー(MV: メインビジュアル)など、スクロールして次のセクションへ進むレイアウトでは100svhが適しているケースが多いです。

100dvhを指定すると、ユーザーが下へスクロールし始めた瞬間にアドレスバーが縮み、それに伴ってMVの高さが伸びるため、画面全体が一瞬ガクッと揺れるような違和感を与えることがあります。

全画面メニューやモーダルでは100dvhが使いやすいケース

スクロールを固定する全画面ハンバーガーメニューやモーダルウィンドウ、ページ全体の遷移を伴わないWebアプリ画面などでは100dvhが便利です。

ツールバーの有無に関わらず、現在見えている領域に合わせて広がるため、下部に隙間ができたり見切れたりするのを防ぎやすくなります。

100dvhはスクロール中に高さが変わることがある

dvhはUIの開閉に合わせてサイズが変化します。そのため、スクロールに伴って内部要素の再配置が発生し、描画負荷や操作感の低下につながる場合がある点に注意が必要です。

height: 100dvhよりmin-height: 100dvhが向いている場合もある

全画面表示にする際、無条件にheightを使うと中身がはみ出してしまう(オーバーフローする)トラブルが起こりやすくなります。

heightとmin-heightの使い分け

  • height: 100svh; / height: 100dvh;

    高さを厳密に指定したサイズへ固定します。中身が指定した高さを超えた場合、要素の外へはみ出したり、追加のスクロールが発生したりします。overflow: hidden;などを併用している場合は、内容が見切れる原因にもなります。

  • min-height: 100svh; / min-height: 100dvh;

    「最低でも画面の高さ分を確保する」指定です。中身が少ないときは全画面になり、コンテンツが多いときは中身の分だけ縦に伸びて自然にスクロールできます。

/* 中身の量に応じた実務的な指定 */
.section-full {
  min-height: 100svh;
}

端末の画面サイズが小さい場合や、テキスト量が多くなる可能性があるセクションでは、基本的にmin-heightを選ぶ方がコンテンツの欠損を防ぎやすくなります。

古いブラウザも考慮する場合のフォールバック

dvhやsvhは現在主要なブラウザで広くサポートされていますが、古いバージョンのブラウザへ配慮が必要なプロジェクトでは、CSSのカスケーディング(上書き)を利用したフォールバックを記述します。

.hero {
  /* 古いブラウザ向けフォールバック */
  min-height: 100vh;
  /* 対応ブラウザ向け */
  min-height: 100svh;
}

未対応のブラウザは100svhの行を無視して100vhを適用し、対応ブラウザは後の行にある100svhを適用します。

100dvh・100svhに変えても直らないときの確認点

単位を100svhや100dvhに変更したにもかかわらず「なぜか縦スクロールバーが出る」「余白が生じる」という場合は、CSSのボックスモデルや周辺要素の影響を疑ってみてください。

1. paddingやmarginを含めて画面高さを超えていないか

box-sizing: content-boxの状態でheight: 100svh;に上下のpaddingやborderを追加すると、実際の外寸が100svhを超えます。また、上下のmarginを持つ場合も、ページ全体として画面高さを超える原因になります。

2. box-sizingの指定を確認する

paddingやborderを高さの内側に含めるため、対象要素(またはリセットCSS全体)にbox-sizing: border-box;が効いているか確認してください。

*, *::before, *::after {
  box-sizing: border-box;
}

3. ヘッダーやフッターを含めて100svhを超えていないか

通常フロー内のヘッダーが80pxあり、その下のmain要素へheight: 100svh;を指定すると、ページ全体では「100svh + 80px」となりスクロールが発生します。

calc()を使ってバーの高さ分を引くか、Flexbox/Gridでレイアウトを組む必要があります。

.main-content {
  height: calc(100svh - var(--header-height));
}

4. overflowの指定を確認する

子要素のネガティブマージンやtransformアニメーション、はみ出た装飾要素によってスクロールが発生していないか確認します。必要に応じて親要素にoverflow: hidden;やoverflow-x: hidden;を検討してください。

5. safe-areaの影響を受けていないか

iPhoneのノッチやホームインジケーター付近では、100dvhや100svhだけでは重要なUIが安全領域内に収まるとは限りません。

padding-bottom: env(safe-area-inset-bottom);などを使用している場合は、その余白を含めた高さ設計になっているかも確認してください。

JavaScriptでwindow.innerHeightを使う方法はまだ必要?

以前は、スマホでビューポートの高さが期待通りにならない問題へ対応するために、以下のようなJavaScriptでカスタムプロパティを更新する手法がよく使われていました。

// 過去によく使われたJSでの高さ調整例
const setAppHeight = () => {
  document.documentElement.style.setProperty(
    '--vh',
    `${window.innerHeight * 0.01}px`
  );
};

setAppHeight();
window.addEventListener('resize', setAppHeight);
/* CSS側での参照 */
.full-screen {
  height: calc(var(--vh) * 100);
}

現在の結論

多くの通常Web制作においては、このJavaScript実装は不要になっています。

  • 理由1:svhやdvhがCSS標準として広く普及したため。
  • 理由2:JavaScriptでviewport変化を監視してCSS変数を書き換える実装は、CSSだけで完結する方法に比べてコードが複雑になり、保守コストが増えるため。

ただし、「画面回転時に極めて特殊な再計算を行いたい」「古いWebView環境で確実に特定のUI高さを同期させたい」といった特殊な要件がある場合は、現在でもピンポイントで活用されることがあります。

まとめ

スマホで100vhの高さがずれる問題は、新しいビューポート単位を適切に選ぶことで解決できます。

  • ファーストビューや通常のセクション:スクロール時のガタつきを防ぎやすい100svh(またはmin-height: 100svh;)
  • モーダルや全画面メニュー:現在の表示領域に合わせやすい100dvh
  • 内容量に応じた配慮:テキストのはみ出しを防ぐため、状況に応じてmin-heightを検討する

まずはsvh / dvhで対応し、それでも余白や意図しないスクロールが発生する場合はbox-sizingやpadding、ヘッダー分の高さ計算を順に確認してみてください。