Capacitor で作った Android アプリで、画面全体がスクロールできなくなった。原因は overscroll-behavior でした。 同じ症状で困っている方に届くよう、症状と原因と対処を残しておきます。
症状
拙作の単語クイズゲーム「ワードバトル」の v1.1.3 を配信した直後、こんな状態になりました。
- 指でドラッグしても、画面がまったくスクロールしない
- ただしタップは効く。ボタンは普通に押せる
- ページ自体はスクロール可能な状態にある(
window.scrollTo()で動く) - キーボードの PageDown でも動く
- パソコンの Chrome で同じページを開くと、何の問題もなくスクロールできる
つまり「スクロール機能が壊れている」のではなく、指のドラッグだけが効かない。この紛らわしさが厄介でした。
原因
CSSに書いたこの一行です。
overscroll-behavior-y: none;
Android の WebView では、overscroll-behavior-y に none を指定すると、オーバースクロール(引っ張った時のたわみ)だけでなく、ページ全体のタッチスクロールごと無効になります。
contain でも同じでした。auto(=指定しない)だけが正常に動きます。
なぜ気づけなかったか
パソコンの Chrome では、まったく同じ指定で普通にスクロールできるからです。
これが最大の罠でした。ブラウザで開いて A/B 比較しても、絶対に再現しません。
実際、私たちは一度この行を疑って、Chrome で比較して「overscroll は無罪」と誤った結論を出しています。そして無関係な別の要素(広告の下敷きに使っていた .ad-underlay)を犯人だと思い込み、一晩を溶かしました。
最終的に決着がついたのは、アプリの中の WebView に開発者ツールを繋いで、実機で直接確かめた時です。ブラウザの再現テストでは永久に分からなかった。
なぜその一行を書いたのか
念のため、書いた理由も残しておきます。
テスターの方から「画面端で引っ張ると、広告バナーの隙間からアプリの背景が見える」という報告をもらっていました。オーバースクロール時のたわみで隙間が露出する、という現象です。
それを抑えるために overscroll-behavior-y: none を入れました。目的は正しかったわけです。
ただし後から分かったのは、この指定を外しても隙間の症状は再発しなかったということでした。
実際に隙間を塞いでいたのは、同じバージョンで入れたもう一つの修正(広告下敷きの高さを広告の高さぶん拡張した変更)のほうで、overscroll-behavior は重ねがけの保険にすぎなかったのです。
保険のつもりで書いた一行が、本体を殺していた。
対処
Capacitor アプリでは overscroll-behavior を書かない。 これに尽きます。
どうしてもオーバースクロールのたわみを消したい場合は、CSS ではなく Android 側で指定します。
getBridge().getWebView().setOverScrollMode(View.OVER_SCROLL_NEVER);
CSS で解決しようとすると、今回のように WebView 特有の挙動を踏み抜きます。プラットフォーム固有の挙動は、プラットフォーム側のAPIで扱うのが安全です。
教訓:ブラウザでの再現テストを信用しすぎない
ハイブリッドアプリ(WebView でWebページを動かす形式のアプリ)を作るうえで、今回いちばん学んだのはこれです。
「Chrome で再現しない」は「バグではない」の証明にはならない。
Capacitor や Cordova を使うと、開発中はブラウザで確認するのが圧倒的に速くて楽です。私もそうしていました。でも WebView は Chrome とは別物で、タッチまわりの挙動には実際に差があります。
同じ罠にはまらないために、私の中でのルールはこうなりました。
| 確認すること | どこで確認するか |
|---|---|
| レイアウト崩れ、文字化け、機能の動作 | ブラウザでよい |
| タッチ・スクロール・キーボード・広告 | 必ず実機の WebView で |
その後
原因を特定した翌日、overscroll-behavior を削除した v1.1.4 を配信しました。審査を通過し、私の実機でスクロールが復活。
そして数日後、症状を報告してくれたテスターの方からも「改善されているのを確認しました」という返信をもらいました。
一晩溶かした甲斐がありました。
この記事のアプリ:ワードバトル(紹介ページ)

コメント