[CSS]スマホで要素を高さいっぱいに表示したいのに、期待通りに表示されない時の解決方法
Post on:2018年8月8日
ビューポートを使った単位(vw, vhなど)は、特にスマホの各ブラウザにおいて複雑です。例えば、vwのスクロールバーを考慮する必要がありますか? サイトのナビゲーションやページコントロールはどうでしょうか? それらは計算に含まれますか?
スマホで要素をビューポートの高さいっぱいに表示したいのに、期待通りに表示されない時の解決方法を紹介します。
The trick to viewport units on mobile
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
ビューポートの高さ(vh)の仕様
W3Cの仕様では、ビューポートの単位をどのように計算するかは漠然としています。スマホでは高さが重要になることが多いので、ビューポートの高さ(vh)を見てみましょう。
- ビューポートの高さ(vh)とは
- Equal to 1% of the height of the initial containing block.
- 初期包含ブロックの高さの1%に等しい。
仕様はこれだけです。
デバイスやブラウザ固有の仕様を扱う際の明確な説明はありません。
ビューポートの高さ(vh)は、ブラウザの現在のビューポートを元に計算されます。ブラウザを開いてWebページをロードし始めた場合、1vhはスクリーンの高さ(ブラウザのUIを差し引いた値)の1%になります。
しかし!
これはスクロールをすると、話は異なります。コンテンツがアドレスバーのようなブラウザのUIを通り過ぎた途端、vhの値が更新されます。その結果、レイアウトが期待通りにならないことがあります。
左: アドレスバーが表示されていると下が隠れる、右: 期待される表示
Safari for iOSは、スクリーンの最大の高さを元にvhの固定値を定義されるよう実装された最初のスマホ用ブラウザです。これにより、アドレスバーが表示されなくなっても、問題はありません。Chromeのスマホ用ブラウザでは1年ほど前にこれが実装されました。
参考: URL Bar Resizing、デモページ
この記事の執筆時点では、Firefox Androidでこれを解決するチケットがあります。
参考: Bugzilla
高さいっぱいの要素を表示する方法
この問題は、CSSの変数とJavaScript数行で解決します。
JavaScriptでは、グローバル変数window.innerHeightを使用して、常に現在のビューポートの値を取得できます。この値はブラウザのUIが考慮されており、可視性が変わると更新されます。それを利用して、ビューポート値をCSSの変数に格納し、vhユニットの代わりに要素に適用させます。
下記のコードではCSSの変数を--vhとし、高さを指定します。
1 2 3 4 |
.my-element { height: 100vh; /* 変数をサポートしていないブラウザのフォールバック */ height: calc(var(--vh, 1vh) * 100); } |
JavaScriptで、ビューポートの内側の高さを取得します。
1 2 3 4 |
// 最初に、ビューポートの高さを取得し、0.01を掛けて1%の値を算出して、vh単位の値を取得 let vh = window.innerHeight * 0.01; // カスタム変数--vhの値をドキュメントのルートに設定 document.documentElement.style.setProperty('--vh', `${vh}px`); |
JavaScriptでビューポートの高さを取得し、vhに使用できる値を得ることができました。変数--vhの値はドキュメントのルートに設定しておくとよいでしょう。
その結果、他のvhユニットと同じように--vhを高さの値として使用し、100を掛けて、私たちが望む完全な高さにすることができます。
See the Pen Viewport Height on Mobile (no resize on update) by CSS-Tricks (@css-tricks) on CodePen.
ビューポートのリサイズにも対応する
上記で完了したように見えるかもしれませんが、これだけではビューポートの高さが変化した時に要素のサイズは更新されません。上記のデモでサイズを変えてみてください。
リサイズに対応するには、windowのresizeイベントを取得することで、--vhの値を更新できます。ユーザーがデバイスを縦向きから横向きに回転させた場合やナビゲーションがスクロールのビューから移動する場合に便利です。
1 2 3 4 5 6 |
// resizeイベントの取得 window.addEventListener('resize', () => { // あとは上記と同じスクリプトを実行 let vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); }); |
--vhの値を更新すると、ページが再描画され、その結果として要素が隠れてしまうかもしれません。そのため、ビューポート単位の正確な値が必要な場合にのみ使用してください。
また、ユーザーがブラウザのサイズを変更している間に多くのイベントが発生するのを避けるために、resizeイベントのデバウンスメソッドを実装することもできます。
参考: Debouncing and Throttling Explained Through Examples
See the Pen Correct Viewport Height on Mobile by CSS-Tricks (@css-tricks) on CodePen.
上記のデモでは、ビューポートの高さを変化させた時に、それに応じてCSSの変数が更新されて要素のサイズも変化します。
私は最近、このテクニックをプロジェクトで使用し、本当に助かりました。ブラウザのデフォルトの動作を扱う時は、常に2回考えるすべきです(::focusは特に注意が必要です。参考: Focusing on Focus Styles)。同様に、ブラウザは最近高い頻度でアップデートされる傾向なので、今日のソリューションは明日は機能しない可能性があることにも注意してください。
この記事があなたに役立つことを願います。
sponsors