こんにちは。アイレットデザイン事業部の長谷です。

本記事では ビューポート とそれを基準にした CSS の単位 vh を中心に、モバイル特有のUI問題をどう解決するかをまとめました。スマホサイトのレイアウトで困った経験がある方は、ぜひ参考にして頂ければと思います。

0.目次

本記事は「ビューポートとvh」に関わる様々な情報を詰め込んだノイズが多い記事です。そのため、とりあえずvhの使い方だけ知りたいなどの場合は、3〜5章を読んでいただければ十分だと思います。

1. はじめに

height: 100vh; を指定したらスマホで下がはみ出してしまう……」そんな悩みを抱えたことはありませんか?原因はモバイルブラウザの UI バーが表示・非表示でビューポートの高さが変わることにあります。その問題を解決するにあたり svh / lvh / dvh という新しい単位が存在します。

まずはそれらの単位の基本になる “ビューポート” から順に見ていきましょう!

note

本記事では、主にモバイルのUIにおける「ビューポート」「vh系単位」の基本を説明します。

2. ビューポートとは?

ビューポート はブラウザがコンテンツを描画する領域です。デスクトップではウインドウの内側、モバイルではアドレスバーやツールバーなどを除いたコンテンツの表示領域を指します。いわゆる、ブラウザ自体の UI を除いた領域のことです。

モバイルのブラウザでページを表示してみると、以下のような形式になることが多いです。

モバイル画面において、ビューポートがどこかを図解する画像

 

ビューポートの大別分類

種類 説明
Visual Viewport 端末画面に実際に表示されている領域。ピンチズームで数値が変わる。
Layout Viewport CSS レイアウト計算の基準。window.innerHeight が返す値。

ビューポートに含まれない要素

基本的にブラウザ自体の UI は含まれません。

  • タイトルバー
  • アドレスバー
  • ブックマークバー/ツールバー
  • メニューバー
  • タブバー
  • ステータスバー
  • デベロッパーツール
    など

ビューポートに含まれる要素

基本的にブラウザ自体の UI 以外を含みます。

  • HTML コンテンツ
  • 画像、テキスト、動画などのウェブコンテンツ
  • ウェブサイトが表示するスクロールバー

2‑1. meta viewport の基本設定

スマホで PC サイトを開くと文字が極端に小さくなるのは、モバイルブラウザが初期ズームを 980 px 幅に設定するからです。この挙動をコントロールするのが <meta name="viewport"> タグです。

基本的に以下を設定しておけば問題ないと思いますが、場合によっては後述するviewport-fit=coverを設定することもあります。

 

属性 役割
width レイアウト幅の基準 device-width (端末幅)
initial-scale 初期ズーム倍率 1 (等倍)
user-scalable ピンチズームの可否 yes / no
viewport-fit ノッチ周りの描画範囲 cover (セーフエリア拡張)

ポイント

アクセシビリティの観点から user-scalable=no は推奨されません。視覚補助で拡大したいユーザーがいるためです。

使い分け早見表

目的 設定例
基本形 <meta name="viewport" content="width=device-width, initial-scale=1">
ノッチ対応 (PWA/フルスクリーン) <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
拡大禁止 (非推奨) <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

viewport-fit=coverの有無は、モバイルの縦持ち画面だと効果が分かりづらい気がします。しかし、モバイルを横持ちにすると顕著に違いがわかります。昨今のiPhoneをはじめとしたモバイルは、端末自体も色々変わっていて、マイクやカメラの横幅高さもや見た目が変わっていたり、ホームボタンがなくなったり、様々な変化が起きています。

例えば、headerに背景色をつけていて、bodyにも別の背景色をつけている場合、viewport-fit=coverが指定されていないと、横持ち画面ではheaderの背景色が画面いっぱいには表示されません。
これは、マイク・カメラなどのシステムUIの領域は、SafeAreaとして、位置付けられているためです。

viewport-fit=coverを指定することで、SafeAreaの範囲を拡張することができるため、headerの背景色が画面いっぱいには表示されます。

ただし、サイト上のテキストなどがシステムUIに被らないように、CSS の env(safe-area-inset-*) 変数でノッチやホームバー領域の余白を制御したり、max-widthなどでコンテンツの幅を制御したり工夫する必要があります。

3. vh とは?

vhとは、端的に言うと「ビューポートの高さ」を表す単位であり、vhは「ビューポート高さの 1 %」を表す単位になります。

100vhの表示問題

モバイル Safari などでは UI バーが出ている状態でも 100vh非表示時の高さ を基準に描画するため、下に余白ができたり要素が隠れたりします。

3-1. 新しいvh系の単位

単位 定義 代表的な用途 注意点
svh UI バー 表示 時の最小高さ ヒーローセクション UI バー非表示時に余白が出る
lvh UI バー 非表示 時の最大高さ フルスクリーン表示 UI バー出現時に要素が切れる
dvh 状態に応じて動的変化 (svhlvh) モーダル・オーバーレイ 背景画像が伸縮することがある

3-2. 各単位の使い分け

  1. UI バーが出ても隠れたくないsvh
  2. 常時フルスクリーンで見せたいlvh
  3. UI 状態に追従させたいdvh
  4. 旧ブラウザ対応必須100vh をフォールバック

3-3. 単位別のデモ

以下は vh / svh / lvh / dvh をそれぞれ指定したときのシンプルな挙動を確認するサンプルコードです。

https://codepen.io/nunllunb-the-reactor/pen/xbbyRQv

テスト方法
スマホ実機などで、アドレスバーの開閉を繰り返し、各セクションの高さ変化を観察してみましょう。

3-4. ブラウザ対応状況 (2024‑05 時点)

基本的に主要ブラウザ(Chrome、Safari、Edge、Firefox)の最新バージョンは問題なく対応しています。

ただし、svh/lvh/dvhはブラウザのバージョンによっては未対応のため、vh単位をフォールバックして使用するなどする必要があります。

4. コンテナクエリとその他の代替手段

4-1. Q&A「コンテナクエリとの使い分けは?」

  • ビューポート単位: 画面全体を基準にしたレイアウトに最適。
  • コンテナクエリ: 親要素サイズ基準でコンポーネント単体をレスポンシブに最適化。

両者を組み合わせることで、柔軟かつ保守性の高いデザインが実現できます。

4-2. Q&A「vh は Sass の mixin で代替できる?」

結論から言うと 代替ではなく補完 が近いと思います。Sass では以下のような mixin を用意し、clamp() + vw などで ビューポート幅に応じて フォントサイズやパディングを滑らかに変化させることが可能です。

@mixin fluid-size($min, $max) {
font-size: clamp(#{$min}, #{calc($min + 1vw)}, #{$max});
}

しかし vhdvh など 高さ基準 の単位は純粋な数値であり、mixin で完全に置き換えることはできません。mixin は複雑な計算やフォールバック記述を 簡潔に再利用 する目的で使い、ビューポート単位自体は CSS レベルで記述するのがいいと思います。

ポイント

Sass は「ロジックの自動化」、vh 系単位は「レイアウトの基準」。役割レイヤーが異なるため置き換えではなく“いいとこ取り”で活用する。

4-3. Q&A「Tailwind CSS で svh / dvh / lvh は使えるの?」

Tailwind CSS v3.4+(2024‑02 リリース)からは Matching Viewport 系ユーティリティがコアに追加され、追加設定なしで h-svh / h-lvh / h-dvh が使えます。

 

クラス 出力される CSS
h-svh / min-h-svh height: 100svh; / min-height: 100svh;
h-lvh / min-h-lvh height: 100lvh; / min-height: 100lvh;
h-dvh / min-h-dvh height: 100dvh; / min-height: 100dvh;

旧バージョン (<3.4)をお使いの場合、または 25vh など細かな値が必要な場合は、従来どおり arbitrary value (h-[25vh]) や tailwind.config.js でのカスタム拡張を利用してください。

注意

PostCSS のミニファイプラグインが未知の単位を削除するケースは依然としてあるため、ビルド設定を確認しましょう。

5. ビューポート単位はどのように機能しているのか?

結論
vh などのビューポート単位は JavaScript ではなくブラウザのレンダリングエンジン (Blink・WebKit・Gecko など) がネイティブに計算 しています。

5-1. レイアウトエンジンのワークフロー

  1. スタイル解決
    CSSOM が構築される段階で height: 50vh のような宣言を検出。
  2. ビューポート計測
    window.innerHeight 相当の Layout Viewport サイズを取得。
  3. 長さの正規化
    50 × viewport_height / 100 → 実ピクセル値に変換。
  4. レイアウト
    算出されたピクセル値を使ってボックスツリーを配置。
  5. 描画
    GPU へ渡して実際の画面にレンダリング。

ポイント

ここまで JavaScript は関与しません。C++ などで書かれたブラウザのレンダリングエンジン内部コードが動作しています。

5-2. 画面サイズが変わったら?

端末を回転・ウインドウをリサイズ・モバイルで UI バーが出入り
Resize イベント後にブラウザのレンダリングエンジンによって再度 ①〜⑤ を実行される。

dvh はこの再計算を スクロールイベントと連動 して頻繁に行うため、スクロール位置に応じて高さがスムーズに変わります。

5‑3. JavaScript が関与するケース

デフォルトでは、ビューポートにJavaScriptは関与しません。
しかし以下のケースなどでは、関与する場合もあります。

ケース 役割
SPA フレームワーク ルーターがページ遷移を JS で処理 → ビューポートは物理的には変わらないが、仮想ページ切替でレイアウト再評価が走る
カスタムリサイズ監視 ResizeObserver で自前に監視 → 計算値をカスタムプロパティへ代入するなど JS 補助は可能
デバイス情報取得 window.innerHeight を JS で参照 →UI 表示に合わせたロジックを書ける

5-4. vhdvh のパフォーマンス影響

単位 レイアウト再計算タイミング 体感コスト ベストプラクティス
vh リサイズ/回転 時のみ 極小 (通常体感なし) そのまま使用して OK
svh / lvh UI バー出入りに連動 (発生頻度は中) 影響は限定的。アニメーションには注意
dvh スクロールごとに頻繁 (UI バーが動くたび) 中〜大 (低端末で 1–2 ms/フレーム) ページ全体よりセクション限定で使用し、重いエフェクトと併用しない

まとめ

単位の算出自体はブラウザ内部処理。JS はあくまで“監視”や“補助調整”に使うだけ。

6. まとめ

今回は、vhおよびビューポートとモバイルのUIについて、説明しました。
本記事の内容を端的にまとめると以下になります。

  • vh は便利だがモバイルでは UI バーに問題がある
  • vhの問題を補完する単位としてsvh / lvh / dvh があるため、目的に応じた使用がオススメ
  • 使用する場合は、ブラウザ対応状況も合わせてチェックした方がいい。旧ブラウザには 100vh フォールバックを忘れずに
  • Sass は vh を置き換えるものではなく、フォールバックや複雑な計算をまとめる役目
  • Tailwind CSS は arbitrary value かカスタムユーティリティで dvh 系単位をサクッと導入可能
  • vhをはじめとしたビューポート単位は、ブラウザエンジンによって、機能している

本記事が皆さんのレイアウト調整の一助となれば幸いです!
最後までお読みいただきありがとうございました!

参考記事