はじめに
フロントエンドのバグには、ときとして「コードでは直せない、ブラウザ構造上の限界」に行き着くものがあります。そういうとき本当に必要なのは、単にHTML/CSS/JSをこね続けることではなく、「どこまでがWebで直せる範囲か」を適切に、素早く見極めて、限界の先はUXやプロダクトの判断に委ねることだと思っています。
この記事は、その一例です。モバイルのソフトウェアキーボードで起きたスクロール不具合を、原因まで実測で特定したうえで、最終的に「レイアウトでは直せない」と判断するまでの記事になります。きれいな解決策の紹介ではありません。
(ちなみに筆者はふだん、UIデザインとフロントエンド実装の両方に関わっていますが、こうした「デザイン上は一見うまくできているのに、実機では崩れる」ような問題は、その境目で起きるため調査を担当することが多く、今回もその一件でした。)
なぜこの手のバグは厄介なのか
何が難しいかというと、
- PCのブラウザでは再現しない(ソフトウェアキーボードが無いので当然)
- Chromeのデバイスエミュレーションでも再現しない(ソフトウェアキーボードを「出した状態」を再現できない)
- iOS Safari と Android、さらにアプリ内WebViewでそれぞれ挙動が違う場合がある
つまり「手元で直せない」「直したつもりでも実機で確認するまで分からない」ため、修正と確認の往復に時間がかかり、なかなか前に進まない領域です。
同じ症状に直面した人が、原因の切り分けと「どこで諦めるか」の判断を早められればと思って書いています。
何が起きたか ── チャットの会話が伸びると、先頭に戻れなくなる
問題が起きたのは、画面全体を覆うチャット型のドロワーでした。構造はチャットUIの定石そのものです。
CSSでは、ドロワー本体を position: fixed; height: 100% で全画面に固定し、中身を flex-direction: column で「固定ヘッダー / 伸びるスクロール領域 / 固定入力欄」に分ける、ごく普通の作りです。ソフトウェアキーボードのないPCでは問題なく動いていましたが、iOS・Androidなどのモバイルでは特定の条件下で良くない挙動をします。
症状は、iOS・Android実機で入力欄をタップしてキーボードを出した状態で、次の条件のときだけ現れます。
- 会話が短い(チャット領域があふれていない) … 問題なし。上にスクロールすれば会話の先頭まで見える
- 会話が長い(チャット領域があふれている) … 上にスクロールしても、会話の先頭まで届かない。数百px手前で頭打ちになる
キーボードを出していないとき(全画面)は、どれだけ会話が長くても普通にスクロールできます。「キーボード表示中」かつ「会話が長い」ときだけ、先頭が見られなくなる。この条件の噛み合わせが、原因の切り分けを難しくしていました。
なぜ起きるのか ── 2つのビューポートと、入れ子スクロールの横取り
原因は2つの要素の合わせ技でした。順に説明します。
前提:レイアウトビューポート と ビジュアルビューポート
まず、ブラウザにはサイズの異なる2種類の「ビューポート(表示領域)」があります。
| 何を指すか | キーボードを出すと | |
|---|---|---|
| レイアウトビューポート(layout viewport) | CSSが「画面の大きさ」として基準にする領域。position: fixed や height: 100% はこれを基準にする |
変わらない(iOSの場合) |
| ビジュアルビューポート(visual viewport) | ユーザーが実際に「見えている」領域。キーボードで隠れた分だけ小さくなる | 縮む/上にずれる |
iOSは、キーボードが出てもレイアウトビューポートを縮めません。縮むのはビジュアルビューポート(見えている領域)だけ。そしてブラウザは、入力エリアが下部にあると、フォーカスした入力エリアを中央に寄せようと、見えている領域を上へパン(スクロール)させます。
このとき position: fixed; height: 100% のドロワーはレイアウトビューポート基準なので、キーボードが出たことを知らず、画面全体(キーボードの裏まで)を占め続けます。結果として、ドロワーの上端はキーボードのパンによって「見えている領域」より上へ押し出されます。これが崩れの起点であり、事象を分かりにくくする原因です。
本題:入れ子スクロールが上スワイプを「横取り」する
チャット領域は、それ自体が overflow-y: auto を持つ入れ子の(チャットエリア内の)スクロール領域です。加えて、背面への影響を防ぐために overscroll-behavior: contain(背面へのスクロールの連鎖を止める)も付けていました。
ところが、この構成が会話量によって裏目に出ます。
- 会話が短い(あふれていない):チャット領域は自分ではスクロールできない(チャット領域にスクロールバーが出ていない状態)。だから上スワイプは外側に渡り、ドロワー全体がパンして動く。結果、先頭まで見える
-
会話が長い(あふれている):チャット領域が自分でスクロール可能になる(チャット領域にスクロールバーが出ている状態)。だから上スワイプは内部スクロールに横取りされ、
overscroll-behavior: containによって外側への伝播も止まる。外側(ドロワー全体)は動かない。
そして肝心なのは、内部スクロールをいっぱいまで動かしても、チャットエリア内がスクロールされるだけで、会話の先頭は依然として画面の外に残ってしまうという点です。
あふれてスクロール可能になったチャット領域が上スワイプを奪ってしまうため、動かせるのはチャット領域の内部スクロールだけになります。ドロワー全体を下げて先頭を画面内へ引き戻す動き(外側パン)はもう起きません。そして内部スクロールでは、すでに画面の外へ出てしまった先頭までは戻せない——これが「先頭に戻れない」の正体でした。
一言でいうと:「あふれた入れ子スクロールが上スワイプを横取りするので、画面の外にある先頭に届かない」 です。
どう直そうとしたか
原因が分かってからは、いくつかの手を試しましたが、結論から言うと、JSやCSSでレイアウトをいくら修正しても、納得のいく実装にはたどり着けませんでした。却下した案とその理由を残します。
却下案1:チャット領域を非スクロール化する(height: auto / overflow: visible)
入れ子スクロールが横取りするなら、チャット領域の内部スクロールをやめて、ドロワー全体を動かすスクロールだけに一本化すればいい、という発想です。しかし position: fixed の中身は、ページのスクロールでは送れません。チャット領域を伸ばすと、下端にある入力欄が画面外へ出て、入力欄へ到達不能になりました。✗
却下案2:overscroll-behavior を解除して連鎖を許可する
内部スクロールが上端で止まったあと、その先をドロワー全体のスクロールへ連鎖させれば先頭を出せるはず、という案です。先頭は出せるようになりましたが、さらに上へスクロールするとドロワー全体がテキスト入力エリアごと下がり、今度は入力欄がキーボードの下へ隠れてしまう。加えてスクロールが二段階に感じられ(実際スクロールバーが2つ出ます)、操作感がかなり不自然になりました。✗
唯一の道は「コンテナを可視領域に合わせ続ける」。
キーボードを出したまま成立させる方法は、原理的に1つしかありません。チャットのコンテナ(ドロワー)を、JSで「見えている領域」の大きさ・位置に合わせ続けることです。「見えている領域」は window.visualViewport で取得できるので、その値をドロワーの高さ・位置に当て込みます。こうすればチャット領域の上端が可視領域の中に収まり、先頭にも到達できるようになります。
// ドロワーを、見えている領域(visualViewport)の大きさ・位置に合わせる
const vv = window.visualViewport;
drawer.style.height = vv.height + 'px'; // 高さを可視領域に合わせる
drawer.style.top = vv.offsetTop + 'px'; // 位置を可視領域の先頭に合わせる
理屈は正しいはずでした。ところが実機では安定しませんでした。
- キーボードの種別変更(日本語↔英語)や予測変換バーの増減で高さが変わるたびにリサイズが走り、その度に崩れる。
- キーボードと入力欄の間に出るOS標準のバー(「キーボードを閉じる」ボタンやパスワード一覧ボタンが並ぶ、いわゆる アクセサリバー(input accessory view))の付近がスクロールできてしまい、そこを動かすとレイアウトビューポートが一気に崩れる(このスクロールもWeb側からは制御できない)
さらに iOS に限り、テキストエリアの下にスペースが空いてしまう事象も発覚しました。これは WebKit自身の既知バグでした。
WebKit Bug 292603 ── キーボード絡みで画面下部に不要な「スクロール可能な空白領域」ができる。ステータスは OPEN(未解決)、iOS 15〜26で再現。
overflow/overscroll-behavior/ viewport単位のhackでは抑止できないと報告されている。
つまり、コンテナを可視領域に合わせても、Web側からは原理的に安定させられない。実装が不十分だから直らないのではなく、土台のブラウザが完全に対応しきれていない。ここで「レイアウトで完璧に成立させる」ことは断念しました。
根本的な問題
キーボードを出したまま、「ヘッダー(上端)」と「入力欄(下端)」を同時に可視領域へ収めることは、コンテンツが可視領域より高い限り、レイアウトだけでは実現できない。
外側パンは全体を一緒に動かすので、ヘッダーを見せれば入力欄が沈み、入力欄を見せればヘッダーが隠れる。両立させる唯一の道が「コンテナを可視領域(visualViewport)に合わせる」ことで、それがブラウザの都合で信頼できない以上、要件を満たした実装にたどり着くことはできない、というのが結論でした。
チェックポイント・学び
今回の対応から得たポイントをまとめます。
1. 「キーボード表示中だけ」「特定条件だけ」出る崩れは、ビューポートのズレを疑う
position: fixed / height: 100% はレイアウトビューポート基準で、iOSではキーボードで縮みません。「PCでは出ないのにモバイル(iOS/Android)だけ」「短いと出ないのに長いと出る」といった条件付きの崩れは、まずレイアウトビューポートとビジュアルビューポートのズレを疑うのが近道です。
2. 入れ子スクロール × overscroll-behavior: contain は、条件で挙動が裏目に出る
あふれていないときは外側にスクロールが渡り、あふれるとその場で横取りする。良かれと思って足した overscroll-behavior: contain が、キーボード表示時には「先頭に戻れない」の一因になっていました。入れ子スクロールを使うUIでは、オーバーフローの有無で挙動が変わる前提で設計する必要があります。
3. 実機依存の視覚バグは「推測」ではなく「実測値」で詰める
scrollHeight / clientHeight / visualViewport.height / offsetTop の数値を実機で取得して、「内部スクロール量がxxxpxしかない」「チャット領域の高さが可視領域を上回っている」と分かり、原因が確定しました。推測でCSS/JSをいじって実機確認を往復するより、各数値を採るほうが圧倒的に速い。
4. WebKitのバグトラッカーを確認する
「自分のコードが悪い」と決めつけて直し続ける前に、ブラウザ側の既知バグでないかを確認しましょう。Web側で原理的に直せない問題に時間をかけ続けないために。今回は WebKit #292603 がOPENのままだと知って、解決を諦める判断ができました。
5. 「レイアウトで直す」に固執せず、UXで要件を外す選択を持つ
「キーボードを出したまま全部見せる」という前提を守ろうとする限り、この問題は解けません。前提のほうを外す(チャットを読むときはキーボードを閉じるとする)と、途端に全ブラウザで確実に動く解になります。UIの制約は、実装で殴るより設計で回避するほうが堅牢なことがあります。
おわりに
今回は、原因は実測で特定できたものの、最終的にCSS/JSのレイアウト修正の範囲では直せないと結論づけ、UX側で対応する方針とし、現状は保留にしました。
ただ振り返ると、これは設計判断の誤りというより 「モバイルのキーボード × Webのビューポート機構」という領域そのものが、CSS/JSだけで直そうとするには原理的に難しい のだと思います。WebKit自身がバグを直しきれていないのが、その証拠とも言えます。position: fixed の全画面ドロワー+入れ子スクロールという構造は、チャットUIの定石であって間違いではありません。それでもiOS/Androidのビューポート挙動が信頼できない以上、キーボードを出したまま「先頭もチャット欄も入力欄も全部見せる」レイアウトを安定させることはできませんでした。
UIの「見た目・体験の崩れ」を、ブラウザの実装レベルで原因を切り分け、最後はUX設計で落とす——こういうフローは、Webアプリ開発ではよく直面する課題で、デザインとエンジニアリングのちょうど境界に立っていると、常々頭を悩ます類のものだと感じています。
実機でしか出ないバグは、修正と確認の往復に時間がかかり、対応のコストが見えにくいものです。しかし「これはブラウザ側の制約で、Webからは直せない」と切り分けて説明できれば、レイアウトでの解決を諦めてUXへ倒すことで、無駄な時間を費やさずに済みます。もし同じような問題に直面したときは(あるいはそういう方がいたら)、原因の切り分けと諦めどころの判断にかける時間を、少しでも減らせたら幸いです。
関連バグ:WebKit Bug 292603(キーボード表示時の下部空白領域・OPEN)/ 関連 #191204(キーボード表示時のビューポート機構の破綻)
KIYOラーニング株式会社について
当社のビジョンは『世界一「学びやすく、分かりやすく、続けやすい」学習手段を提供する』ことです。革新的な教育サービスを作り成長させていく事で、オンライン教育分野でナンバーワンの存在となり、世界に展開していくことを目指しています。
プロダクト
- スタディング:「学びやすく・わかりやすく・続けやすい」オンライン資格対策講座
- スタディングキャリア:資格取得者の仕事探しやキャリア形成を支援する転職サービス
- AirCourse:受け放題の動画研修がついたeラーニングシステム(LMS)
KIYOラーニング株式会社では一緒に働く仲間を募集しています




