
CSS でvh
を使用すると、いくつかの問題—アドレスバーとの干渉、ブラウザ別の差異など—が発生し、想定通りの設定とならないことがある。
これは既知の問題であり、Chrome の Ver56 より発生していると言われている。
vh units will be sized to the “largest possible viewport”. This means 100vh will be larger than the visible height when the URL bar is shown.
この記事では、私がこの問題に直面するまでの経緯と、それを解決するために実行した方法――CSSカスタム変数の登録――について述べる。
モチベーション
今回おこなった具体的な対応は、以下の記事の方法をとったに過ぎない。
The trick to viewport units on mobile
しかしより詳しい実装の詳細を知りたかったため、記事にまとめることとした。
はじめに
ネイティブアプリっぽくしたい
以下のような電卓アプリを作成した。
この時にふと、ネイティブアプリのようなデザインにできないものかと考えた。
具体的にはブラウザのタブひとつ分を電卓画面で覆い、これによりスクロールを発生させないことで、ウェブページではなくネイティブアプリっぽい感じを表現できるのではないかと考えた。
benibana2001/kawaii-calculator

vh が狙い通りにいかない
この「ネイティブアプリっぽく見せる目論見」は、コンテンツの高さが全体で100vh
となるようにCSSを設定することで表現した。そしてこれはPC 表示の際はうまくいった(と私は思っている)。しかし iPhone や Android 端末で確認を行うと(PCのDevTool ではなく実機)、なぜか高さが合わなかった。100vhよりも大きい値の高さが発生してしまった。これはそれぞれのブラウザで表示されるアドレスバー
やタブバー
の領域が原因なようだ。 アドレスバーやタブバーの領域の高さの分だけ、画面全体の表示領域が増加してしまっている。これが冒頭で述べた、URL Bar Resizing という問題だ。

The trick to viewport units on mobile
ここでは、この記事の内容を細かく記していく。
TL;DL
はじめに、実際に行った改善の手順を記す。
window.innerHeight
を使用し、アドレスバー、タブバーを除外した高さを取得する。- 1 の高さの 1/100 の値を、CSS 変数として登録する。登録には
CSSStyleDeclaration
API を使用する。 - DOM 要素の高さを
vh
を使用して指定する代わりに、2 で登録した変数を用いて指定する。
これはつまり、vhは当てにならないので、JSを使って正しい値を都度計算し、それをCSSで使用するということである。この方法をとることで、デバイスやブラウザに依存せず、狙った通りの値を設定できる。
以下、詳細を見ていく。
実装1. アドレスバー、タブバーを除外した高さを取得する。
そもそもvh
は、layout viewport の 1%を取得できるプロパティだ。
A vh unit is 1% of the layout viewport’s height. …
(https://developer.mozilla.org/en-US/docs/Web/CSS/Viewport_conceptsより)
しかしvhは、ユーザーによるスクロール操作といった何らかの影響により、正しい値とならないことがある。
Usable vh units on mobile. Prior to this, using vh units meant a page would reflow jarringly everytime the user changed scroll direction.
(URL Bar Resizing より)
特にモバイル端末では、アドレスバーなどの影響が大きく、vh の値が狂う可能性が高い。
そのため、あえてvh
を使用しない。代わりに、window.innerHeight
を使用する。この API は個々の端末やブラウザの種類に影響されず、表示領域のみを正確に算出することが可能だ。そのため不安定なvh
を使用することなく、目的とする値を正確に算出するのに役立つ。

実装2. 1 で求めた値を元に、CSS 変数を登録する。
innerHeight
の値を 100 分割することで、表示領域の 1%を取得することができる。この値こそが、我々が本来欲していたvh
の値だ。
CSSStyleDeclaration
この100分割した値を CSS で使用・設定できるようにするために、CSSStyleDeclarationインターフェースを使用する。これは「スタイル情報やスタイル関連のメソッド、プロパティを提供するインターフェース」であり、一般的には下記のように取得することができる。
|
|
このインターフェースは、あらゆるDOM要素が保有している。そしてこのインターフェースが保有するsetProperty
というメソッドを使用し、CSS変数の登録を行う。
CSSStyleDeclaration.setProperty()
このメソッドは、CSSスタイルブロック宣言に、プロパティを設定するものだ。これを使用することで、自由にStyleを読み取り・書き込みすることができる。
例えば、このメソッドを使用するタイミングにもよるが、DOMのレンダリング前にプロパティを設定することで、JSで作成した変数をカスタム変数としてCSSで使用することが可能だ。
CSSStyleDeclaration.setProperty()
あらゆるDOM要素はこのメソッドを保有しているが、ここではdocument
要素に対して新たな Style(CSSプロパティ) を追加することとする。
ここまでの操作をコードで表すと、以下のようになる。
|
|
実装3. 作成した変数を用いて DOM の高さを指定する。
最後に、上記で定義したカスタム変数をvh
の代わりに使用する。
|
|
CSSでのカスタム変数の使用方法はMDNに丁寧にまとめられています。
おわりに
以上の手順を行うことで想定した通りの viewport の高さ設定が可能となる。
FWやライブラリを使用するときは
Vue.jsやReactのようなライブラリ・FWを使用する場合は、CSSファイルで変数が正常に読み込まれるようにするために、DOMのマウントのタイミングでCSSプロパティを設定するとよいと思います。
今回のソースは、benibana2001/kawaii_caluculatorにあります。Vue.jsのVueインスタンスの内部で定義しているため、仮想DOMがマウントされた段階でCSS変数の定義、登録を行なっています。
MDNの日本語訳について
本記事で何度か引用させていただいているMDN web docsですが、翻訳されていない記事がまだまだあります。興味がある方は、翻訳してみることをお勧めします。
私は現在、記事内でも取り上げたCSSStyleDeclaration
APIの翻訳を行っています。
MDNの翻訳のノウハウに関しては、Mozilla Japanが定期的に開催しているミートアップでを学ぶことができるのでお勧めです。