スマホ向けの画面で大きめ入力フォームを使う時、キーボードの開閉を考慮した高さにしたいなと思うことはままあるかと思います。でもできないんですよねー。CSS の vh
/dvh
はキーボード開閉に反応しません。
どうしたもんかと調べていたところ VisualViewport という仕様が利用できることを知りました。
先にまとめ
- JavaScript の
window.visualViewport.height
で、キーボード分を除いた高さを取れる - 全ブラウザー対応済み
resize
イベントも反応する- CSS 変数(カスタムプロパティ)に保管しておけば CSS からも利用できる
キーボードが出てくると良い感じに縮まるダイアログができた。やったぜ pic.twitter.com/IHKa6xGTmH
— 高梨ギンペイ (@ginpei_jp) August 8, 2023
VisualViewport
DOM じゃなくて CSSOM 方面の仕様である様子。仕様はまだドラフトだけど全てのブラウザーで利用可能です。
- CSSOM View Module – 12. VisualViewport
- VisualViewport API | Can I use… Support tables for HTML5, CSS3, etc
- Window.visualViewport – Web API | MDN
こいつが持つ height
プロパティで目的の高さが取れます。また resize
イベントも発火するので、これを使って高さの変化を検知することも可能です。
const vv = window.visualViewport; vv.onresize = () => console.log(vv.height);
要素の高さへ反映させる
CSS に vvh
みたいな単位があれば良かったんだけどないものはないので、代わりに JavaScript から CSS 変数ことカスタムプロパティとして用意してやるようにします。
const vv = window.visualViewport; const f = () => document.documentElement.style.setProperty( "--visual-viewport-height", `${vv.height}px`, ); vv.addEventListener("resize", f); f();
CSS では var(--visual-viewport-height)
として値をよろしく利用します。
.myDialog { max-height: calc(var(--visual-viewport-height) - 16px); }
React
カスタムフックにしました。CSS 側は一緒。
/** * Sets a var `--visual-viewport-height` to the `<html>` element. */ export function useVisualViewportHeightEffect(): void { useEffect(() => { const vv = window.visualViewport; if (!vv) { document.documentElement.style.removeProperty("--visual-viewport-height"); return; } const f = () => document.documentElement.style.setProperty( "--visual-viewport-height", `${vv.height}px`, ); vv.addEventListener("resize", f); f(); return () => vv.removeEventListener("resize", f); }, []); }
各ページで一度だけ呼び出してください。
useVisualViewportHeightEffect();
おしまい
仕様書の図解を見ると二本指ピンチの拡大でも反応しそうにも見えるけれど、試した限りはそんなことはなさそう。 いや反応するわ。どうしよ。まあいいか。
というわけでキーボードにあわせて画面をぱかぱかできるようになりました。
まだやってないけどキーボードのすぐ上に入力補助のボタンを並べたりもできそう。ほらスマホアプリのマークダウンエディターで記号とかすぐ入力できるボタンが並んでるじゃないですか、あれ。