こんにちは。小学生向けのニュースサイト、こどもニュースをつくっています。
このサイトには記事を縦書きで読む機能があります。縦書きは高さを決めないと行が折り返せないので、本文のブロックに高さを指定しています。
最初は 70vh と書いていました。SNS のアプリ内ブラウザで開いたときに、本文がガタガタと組み直され続けました。
何が起きていたか
アプリ内ブラウザ(WebView)では、スクロールに応じて上下のツールバーが出たり隠れたりします。そのぶん、表示領域の高さが変わります。
vh は表示領域の高さに対する割合なので、ツールバーが動くたびに値が変わります。
横書きなら、高さが少し変わっても見た目は変わりません。中身の高さは文章の量で決まるからです。
縦書きは違います。高さが変わると 1 列に入る文字数が変わり、全部の列が組み直されます。読んでいる最中に文字の位置が動くので、非常に読みにくいです。
しかもツールバーの出入りはスクロールに連動するので、スクロールするたびに組み直しが起きます。
高さを px で固定する
対処は、表示領域に追従させないことです。初回に高さを測って、px の固定値として持ちます。
height: var(--kn-vbody-h, 70vh);
CSS 変数にしておいて、値は JavaScript が入れます。
70vh 直書きだとアプリ内ブラウザ(WebView)のツールバー出入りで高さが揺れて
縦書きが組み直され続けるため、boot script が innerHeight から固定 px を
--kn-vbody-h に入れる(幅変化時のみ再計測)。JS 無効時のみ 70vh フォールバック。
innerHeight から計算した値を入れるので、初回の見た目は 70vh と同じです。違うのは、その後に表示領域が変わっても値が変わらないことです。
フォールバックとして 70vh を残しているのは、JavaScript が無効な環境のためです。CSS 変数の第 2 引数は、変数が未定義のときに使われます。この形なら、スクリプトが動かなくても縦書きは成立します。
再計測の条件
固定してしまうと、画面を回転させたときに高さが合わなくなります。
そこで、幅が変わったときだけ再計測することにしました。ツールバーの出入りでは幅が変わらないので、再計測は走りません。回転や、ウィンドウのサイズ変更では幅が変わるので、そこで測り直します。
resize イベントは、高さだけの変化でも発火します。イベントの発火をそのまま再計測の条件にすると、元の問題に戻ります。「何が変わったときに再計算するか」を自分で決める必要がありました。
初回のペイント前に入れる
この値は、最初の描画の前に入れます。このサイトには、読者の設定(文字サイズ・書体・縦書きか横書きか)を最初のペイント前に反映するブートスクリプトがあるので、そこに相乗りさせました。
後から入れると、70vh で一度組んでから固定値で組み直すことになります。ちらつきの原因が 1 つ増えるだけです。
似た問題は他にもある
この手の「表示領域の高さが動く」問題は、モバイルでは広く知られています。100vh にすると画面からはみ出す、というのが典型です。
いまは dvh / svh / lvh という単位があり、これらを使う手もあります。ただ、dvh は「動的な表示領域」なので、今回のケースでは同じように揺れます。svh(最小の表示領域)なら揺れませんが、ツールバーが隠れているときに余白が出ます。
このサイトでは、初回の値で固定するほうが目的に合っていました。読んでいる最中に組み直されないことが最優先で、多少余白が出るのは許容できます。
単位の選択より先に、「何を固定したいのか」を決めるほうが早い、というのが結論でした。
気づき方
この問題は、手元の PC ブラウザでは再現しません。開発者ツールのモバイル表示でも起きません。ツールバーの出入りが無いからです。
実機のアプリ内ブラウザで開いて、初めて気づきました。
モバイルの表示領域まわりは、実機でしか出ない挙動が多い領域です。とくにアプリ内ブラウザは、通常のブラウザとも挙動が違います。リンクを SNS で共有するサイトなら、一度は実機のアプリ内ブラウザで開いておく価値があります。
まとめ
- アプリ内ブラウザではツールバーの出入りで表示領域の高さが変わる
- 縦書きは高さが変わると全部の列が組み直されるので、
vhを使うと読めなくなる - 初回に測った値を px として CSS 変数に入れ、以降は追従させない
- CSS 変数のフォールバックを使えば、JavaScript が無効でも成立する
- 再計測の条件は自分で決める(幅の変化だけ)。resize イベントをそのまま使うと元に戻る
- 値は最初のペイント前に入れる。後からだと組み直しが見える
- この種の問題は実機のアプリ内ブラウザでしか再現しない