CSSの100vhがスマホでずれるのをdvh・svh・lvhで直す方法

​​CSS​Android,iOS,Playwright

スマートフォンで height: 100vh にした要素の下の端が、アドレスバーの分だけ画面の外にはみ出すのは、vh が「アドレスバーが引っ込んだときの、大きいほうの画面の高さ」を基準にしているためです。トップページのファーストビュー(ヒーロー)や全画面のメニューで、下の端に置いたボタンが隠れる、という形で現れます。100vh を 100svh(小さいほうの高さ)か 100dvh(そのときの高さ)に書き換えると、アドレスバーが出ていても画面に収まる、と仕様では説明されています。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、ヘッドレスの Chromium 153 です。検証に使ったヘッドレスの Chromium には、スクロールに合わせて出たり引っ込んだりするアドレスバーがありません。そのため、アドレスバーによって svh と lvh の値が変わる様子は確かめていません。その部分は CSS の仕様と MDN の説明をもとに書いています。Chromium で確かめたのは、単位の値、古いブラウザ向けの書き方、min-height との組み合わせです。iPhone や Android の実機、Safari、Firefox は試していません。

100vh は、アドレスバーが引っ込んだときの高さ

CSS Values and Units Module Level 4 の仕様では、ビューポート(ブラウザでページを表示する領域)の大きさを3通りに分けています。スマートフォンのブラウザでは、ページをスクロールし始めると、アドレスバーの一部または全部を隠すことがよくある、という例も仕様に書かれています。

単位 基準にする高さ 値が変わるとき
lvh アドレスバーなどが引っ込んだときの高さ(大きいほう) 画面そのものの大きさが変わったときだけ
svh アドレスバーなどが出ているときの高さ(小さいほう) 画面そのものの大きさが変わったときだけ
dvh そのときの高さ アドレスバーが出たり引っ込んだりしたときも変わる
vh lvh と同じ lvh と同じ

いつもの vh は、lvh と同じ大きいほうの高さで決まります。MDN の長さの単位のページにも、vh は lvh と同等だと書かれています。仕様には、Safari にならって多くのブラウザが大きいほうに合わせたこと、いまではウェブの互換性のために lvh に合わせることが必要と考えられていることが書かれています。

このため、アドレスバーなどが出ているあいだは、100vh の要素の下の部分が、その分だけ隠れることがあります。仕様の説明をもとに、3つの単位で箱の高さを指定したときの見え方を図にしました。

上の段はアドレスバーが出ているとき、下の段は引っ込んだときの、スマートフォンの画面の概念図。100vh の箱は、上の段では下の端のボタンが画面の外に出て見えない。100svh の箱は、上の段では画面にぴったり収まるが、下の段では下にすき間ができる。100dvh の箱は、上下どちらの段でも画面にぴったり収まる
アドレスバーが出ているときと引っ込んだときの、100vh・100svh・100dvh の箱の見え方(仕様の説明をもとに描いた概念図)

ファーストビューは min-height: 100svh にする

トップページのファーストビューのように、「最初に開いたときに、1画面にちょうど収めたい」要素には svh が向いています。仕様では、svh の大きさは、アドレスバーなどがすべて出ているときに、中身が隠れずに画面をちょうど満たすと説明されています。アドレスバーが引っ込むと下にすき間ができますが、値はスクロール中に変わりません。仕様はこの性質を、一般に、より「安全」な単位だと表現しています。

css
.hero {
  min-height: 100vh;  /* svh を使えないブラウザ向け */
  min-height: 100svh;
}

height ではなく min-height にしておくと、中身が画面より多いときに要素が伸びます。画面を 400×700px にして、高さ 900px の中身を入れて測ったところ、min-height: 100svh の要素は 900px に伸びました。height: 100svh の要素は 700px のままで、中身が下にはみ出しました(scrollHeight は 900px)。

全画面のメニューや下にボタンがある画面は 100dvh にする

ハンバーガーメニューを開いたときの全画面のパネルや、チャットの入力欄のように画面の下の端に置きたいものには、dvh が向いています。仕様では、dvh を使うと、アドレスバーなどがあってもなくても、中身がビューポートにちょうど収まると説明されています。なお、仕様では、画面に出るキーボードは、ふつうは中身の上に重なって表示され、どのビューポートの単位にも影響しないものの例に挙げられています。

css
.menu-panel {
  position: fixed;
  inset-inline: 0;
  top: 0;
  height: 100vh;  /* dvh を使えないブラウザ向け */
  height: 100dvh;
}

ただし、dvh の値は、画面の大きさが変わらなくても変わります。仕様では、スクロールしている最中に中身の大きさが変わることがあり、使い方によっては、読む人の邪魔になったり、処理の負担が増えたりすると注意しています。また、アドレスバーが動いているあいだ、ブラウザは dvh の値を少しずつ変える必要はなく、出きったときか引っ込みきったときの値で計算してよい、とも書かれています。

ページと一緒にスクロールする要素の高さを dvh で決めると、アドレスバーが出たり引っ込んだりするたびに、その要素の高さが変わります。スクロール中に大きさが変わってほしくない要素は、svh か lvh にします。

隠れてもかまわない背景は 100lvh(100vh)のままでよい

全面に敷く背景の画像や色のように、下の端が少し隠れても困らず、アドレスバーが引っ込んだときにすき間が出るほうが困るものは、lvh(vh でも同じ)のままにします。仕様の説明でも、lvh は、アドレスバーなどが引っ込んだときにビューポートを必ず満たすが、出ているときには隠れる部分がある単位とされています。

ポイント

いまの 100vh を書き換えるなら、まず 100svh に置き換えます。svh はスクロール中に大きさが変わりません。

古いブラウザ向けに 100vh を先に書く

MDN の互換性データ(@mdn/browser-compat-data 8.1.9)では、svh・lvh・dvh は、Chrome(Android 版も)108、Firefox 101、Safari(iOS 版も)15.4 から使えます。

これらのバージョンより古いブラウザにも対応するなら、上のコード例のように 100vh の行を先に書き、その下に新しい単位の行を書きます。CSS Syntax Module Level 3 の仕様では、構文に合わない値の宣言は無効になり、無視されると定めています。そのため、古いブラウザでは 100vh の行が残ります。基本の書き方は「CSSで、要素を画面の真ん中に置く方法」の記事でも確かめています。

検証環境の Chromium は dvh に対応しているので、新しい単位を知らない古いブラウザの代わりに、存在しない単位 qvh を書いて確かめました。どの宣言が効いたか分かるように、新しい単位の側は 50 にしています。画面の高さは 700px です。

書いた CSS 要素の高さ
height: 100vh; height: 50dvh; 350px(下の行が効いた)
height: 100vh; height: 50qvh; 700px(下の行が無視され、100vh が残った)
height: 50dvh; height: 100vh; 700px(順番が逆だと、dvh に対応していても 100vh になる)

順番を逆にすると、新しいブラウザでも 100vh が使われてしまいます。新しい単位の行は必ず後に書きます。

1行ずつ並べる代わりに、@supports で分けることもできます。検証環境では CSS.supports('height', '100dvh') が true になりました。次の CSS の 100dvh を 50dvh に変えて試したところ、高さは 350px(@supports の中の側)になりました。

css
.panel {
  height: 100vh;
}

@supports (height: 100dvh) {
  .panel {
    height: 100dvh;
  }
}

検証環境のヘッドレス Chromium では、4つの単位がどれも同じ値になる

検証環境のヘッドレス Chromium では、アドレスバーが出たり引っ込んだりしません。Playwright でスマートフォンの画面をまねて、100vh・100svh・100lvh・100dvh の要素の高さを測ったところ、次のようになりました。

まねた端末 画面(screen)の高さ 表示領域の高さ 4つの単位の高さ
Pixel 7 915px 839px どれも 839px
iPhone 15 852px 659px どれも 659px

Playwright の端末の設定では、表示領域の高さが、画面の高さより小さい決まった値になっています。アドレスバーが動くことはないので、svh と lvh も同じ値でした。表示領域(ビューポート)の高さを 700px から 640px に変えたときは、4つの単位がそろって 700px から 640px に変わりました。

そのため、アドレスバーとの重なりが直ったかどうかは、スマートフォンの実機で、ページを開いた直後(アドレスバーが出ている状態)と、少しスクロールしたあと(引っ込んだ状態)の両方を見て確かめます。

下の端のボタンが、iPhone のホームバーに重なる場合の対処は、「CSSで、iPhoneのノッチやホームバーに隠れないようにする方法」の記事で確かめています。

​Posted by 86