CSSのsvh・dvhが全ブラウザ対応。iOS・Androidの画面いっぱいに要素を広げる最適解
iOS SafariやAndroid Chromeの画面の高さいっぱいにヒーローイメージを表示するという表現は、よく見かけます。

高さをいっぱいに広げるのに100vhを使うと、不要なスクロールが発生し、意図通りに表示されません。 この問題を解決するために、特殊なCSSを使ったりJSを使ったりと、開発の現場では多くの苦労がありました。
本日(2022/11/30)リリースされたGoogle Chrome 108で対応したsvhを使えば、手軽に画面いっぱいのヒーローイメージを作れます。
.hero-image {
height: 100svh;
}
Safari・Firefoxでは対応済み、Chromeと中身が同じEdgeは12/1週リリースの108で対応するので、全ブラウザで使える時代が来ます。もちろん、iOS・Androidも対応です。
本記事では、svhの使い方、同様に使えるようになったdvhやsvmaxなどの違い、従来の手法のデメリットをデモを交えて紹介します。
svhを使ったデモ
次のようなコンテンツを作ってみましょう。ページにアクセス時、ヒーローイメージがブラウザの高さいっぱいに広がり、スクロールすると後続コンテンツが見えるようなものです。


HTML コードは次のとおりです。
<main>
<!-- ヒーローイメージ -->
<div class="hero-image">
<img src="ヒーローイメージ用画像" />
</div>
<section>
<h1>ABOUT</h1>
<!-- 以下、後続コンテンツ -->
</section>
</main>
ヒーローイメージは.hero-image要素ですが、CSSで行うことはheight: 100svh;を指定するだけです。
.hero-image {
height: 100svh;
}
iOS Safari、Android・デスクトップのChromeを始め、各環境で動作します。
▼ iOS Safariでの動作結果

▼ Android Chromeでの動作結果

▼ デスクトップのChromeでの動作結果

デモは次から確認できます。
svhとは
従来使われてきたvhやvwとは、ビューポートに対する高さや幅を指します。
デスクトップブラウザのようなビューポートのサイズが固定のものでは十分だったのですが、iOS Safariなどではビューポートのサイズが可変になります。
このうち、ビューポートの高さが最小になった場合の高さを示すのがsvhというわけです。
ビューポートが大きい場合の高さを取得したい場合は、lvhを使います。

また、ビューポートの大きさが変わったときの高さを取得したい場合は、dvhを使います。100dvhを今回のメインビジュアル表現に使ってしまうと、スクロール時にメインビジュアルの高さが変わってしまうため、向いていません。100dvhは、全画面に表示するモーダルウインドウやナビゲーションを作る際に役立つでしょう。
その他の単位については後述します。
なぜ100vh・100%・-webkit-fill-availableでは駄目なのか?
今回作った表現は、100vhや100%では駄目なのかと思う人もいるでしょう。従来の表現を100svhと比べてみましょう。
100vhだと はみ出る
次のように100vhを指定したとします。
.hero-image {
height: 100vh;
}
この場合、ヒーローイメージ部分(.hero-image)はファーストビューからはみ出します。iOS Safariの場合、100vhは100lvhと同じ高さになり、大きいビューポートの高さを取ってしまうのです。

ヒーローイメージがはみ出しているので、スクロールしてもヒーローイメージ部分が続いてしまいます。

たまに勘違いされますが、ビューポートの大きさが変わるのはiOSだけではありません。AndroidのChromeでも、同様にビューポートの大きさが変わるため、100vhでははみ出ます。
▼ Android Chromeで100vhを指定した結果

100%は煩雑
次のように100%を指定したとします。
.hero-image {
height: 100%;
}
HTMLは次のようになっていることに注意してください。
<main>
<!-- ヒーローイメージ -->
<div class="hero-image">
<img src="ヒーローイメージ用画像" />
</div>
<section>
<h1>ABOUT</h1>
<!-- 以下、後続コンテンツ -->
</section>
</main>
この場合、ヒーローイメージ部分(.hero-image)はファーストビューの高さに足りません。

理由はヒーローイメージ部分(.hero-image)が外側のmain要素の高さいっぱいまでしか広がらないためです。
問題を解決するためには、祖先要素すべてに100%を設定する必要があります。煩雑ですし、ヒーローイメージ部分(.hero-image)の事情だけのためにhtmlやbodyにheight: 100%を指定するのはあまりよろしくないでしょう。
/* 祖先要素すべてへ高さ指定 */
html,
body,
main {
height: 100%;
}
.hero-image {
height: 100%;
}
-webkit-fill-availableは癖が強い
次のように-webkit-fill-availableを指定する方法もありました。
.hero-image {
height: -webkit-fill-available;
}
この方法では確かにヒーローイメージ部分(.hero-image)はファーストビューの高さになりますし、祖先要素への高さ指定も不要です。
「デバイスの向きを変えたときに高さを再計算してくれない」という記事も稀に見かけますが、今現在のiOS Safariでは解消されています。
本手法の欠点の1つ目は、ChromeやFirefoxに対応していないことです。次のように処理を分岐する必要がありました。
.hero-image {
height: 100vh;
}
@supports (-webkit-touch-callout: none) {
.hero-image {
height: -webkit-fill-available;
}
}
欠点の2つ目は、「高さ50%」のような指定ができないことです。ビューポートの50%にヒーローイメージを広げたいみたいな表現のときに不便です。
▼ 動作しません
.hero-image {
height: calc(-webkit-fill-available * 0.5);
}
JavaScriptを使う手法もありますが、いずれもsvhの便利さには適いません。
他にもいろいろな単位が使えるようになった
svh・lvh・dvh以外にも、さまざまな単位が使えるようになりました。
vmax・vminについて
以前からvmax・vminという単位が使えました。おさらいしておくと、次のとおりです。
-
vmax: ビューポートのうち、幅か高さの大きい方 -
vmin: ビューポートのうち、幅か高さの小さい方

sv*やdv*と組み合わせられるので、svmaxやlvminといった単位が使えるようになります。
vi・vbについて
「論理的プロパティ(CSS Logical Properties)」に応じたvi・vbにも対応しました。
-
vi:インライン方向 -
vb:ブロック方向
sv*やdv*と組み合わせられるので、sviやlvbといった単位が使えるようになります。
ビューポート単位のチートシート
以上をまとめると、次のようにして各ビューポートのサイズを作成できます。
- ビューポートのサイズは?
- 小さい方:
svから始まる - 大きい方:
lvから始まる - 可変サイズ:
dvから始まる
- 小さい方:
- 幅か高さか?
- 幅:
wをつける - 高さ:
hをつける - 小さい方:
minをつける - 大きい方:
maxをつける - インライン方向:
iをつける - ブロック方向:
bをつける
- 幅:
各単位をまとめると次のとおりです。
- 小さなビューポート
-
svw,svh,svi,svb,svmin,svmax,svi,svb
-
- 大きなビューポート
-
lvw,lvh,lvi,lvb,lvmin,lvmax,lvi,lvb
-
- 可変のビューポート
-
dvw,dvh,dvi,dvb,dvmin,dvmax,dvi,dvb
-
対応ブラウザ
各単位の対応ブラウザは次のとおりです。
- Chrome 108で対応(Android・デスクトップ共に)
- Safari 15.4で対応(iOS・デスクトップ共に)
- Firefox 101で対応
- Edge 108で対応
Edge 108については、2022/12/1週にリリースが予定されています リリースされました(2022/12/6)。
▼ Edge 108での動作結果

最後に
iOS Safariの画面の高さいっぱいにヒーローイメージを広げる問題は、長年開発者を悩ませてきたものです。svh・dvhはその課題を解決できる単位で、長らく全ブラウザ対応が待たれてきました。今回のChromeの対応により、手軽に使える環境が整ったと言えるでしょう。
今回のウェブサイトの素材は、デザイナーの松下 絵梨さんに作成いただきました。
関連資料


Discussion