スマホ向けの画面で大きめ入力フォームを使う時、キーボードの開閉を考慮した高さにしたいなと思うことはままあるかと思います。でもできないんですよねー。CSS の vh/dvh はキーボード開閉に反応しません。

どうしたもんかと調べていたところ VisualViewport という仕様が利用できることを知りました。

先にまとめ

  • JavaScript の window.visualViewport.height で、キーボード分を除いた高さを取れる
  • 全ブラウザー対応済み
  • resize イベントも反応する
  • CSS 変数(カスタムプロパティ)に保管しておけば CSS からも利用できる

VisualViewport

DOM じゃなくて CSSOM 方面の仕様である様子。仕様はまだドラフトだけど全てのブラウザーで利用可能です。

こいつが持つ 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();

おしまい

仕様書の図解を見ると二本指ピンチの拡大でも反応しそうにも見えるけれど、試した限りはそんなことはなさそう。 いや反応するわ。どうしよ。まあいいか。

というわけでキーボードにあわせて画面をぱかぱかできるようになりました。

まだやってないけどキーボードのすぐ上に入力補助のボタンを並べたりもできそう。ほらスマホアプリのマークダウンエディターで記号とかすぐ入力できるボタンが並んでるじゃないですか、あれ。

参考