エディタを作っていると iOS Safari でキーボードを開いたときの挙動は本当に大変ですよね。 この記事では、キーボードを開いたままスクロールしてもフッターを固定できるようなワークアラウンドを最近見つけたので紹介します。 Safari 16 以降が必要。キーボード開閉時に追従が遅れる挙動は改善しません。
デモは https://mechairoi.github.io/ios-safari-virtual-keyboard/。iPhone か iOS Simulator でお試しください。
GitHub - mechairoi/ios-safari-virtual-keyboard
Virtual Keyboard の挙動
Virtual Keyboard を開いたときには以下のようなことが起こっていると推測しています。
- Visual_Viewport_API で取得できる Visual Viewport のサイズがキーボードを除いた領域のサイズまで小さくなります。
- レイアウトの計算をするときにはキーボード表示前の Visual Viewport が使われます。vh 単位や fixed, sticky 要素の位置などは表示前と変わりません。
- スクロールしたときに、キーボード表示前の Visual Viewport のうち画面に表示されている位置が変わることがあり、fixed 要素が動いて見えます。
- Layout Viewport の上に大小二つの Visual Viewport が重なっていてそこから見ている感じです、ややこしいですね。
- Layout Viewport の下?に謎の余白が追加されます。下にスクロールし続けると余白が表示されます。
- キーボードと重なる位置にある入力要素をタップしてキーボードを開いたとき、キーボードに隠れないようにするためかスクロールが発生します。
- また、キーボードを開いている状態で他の入力要素等にフォーカスが移動したときにもスクロールが発生します。
ワークアラウンド
まず画面全体にスクロール可能な要素を表示します。さらにSafari 16 から使えるようになった overscroll-behavior: contain
をすることでスワイプやフリック操作による window
のスクロールを防ぎます。window
がスクロールしなければ固定した要素が動いて見えることはありません。
#root { overflow-y: scroll; overscroll-behavior: contain; width: 100svw; height: 100svh; }
キーボード開閉時などどうしてもスクロールが発生してしまうタイミングでは JavaScript で計算して要素を移動したりするのはこれまでの方法と変わらないです。
ダイアログなど他の要素を画面に固定したいときは position:sticky
を使うか、position:fixed
か position:absolute
で固定した要素の内側もスクロール可能にしてoverscroll-behavior: contain
します。overscroll-behavior: contain
しないと固定した要素上でスワイプしたときにwindow
がスクロールしてしまいます。また選択範囲のすぐ隣にツールバー出すときはコンテンツに対して固定できれば良いので position:relative
と position:absolute
が使えます。
他にも以下のような工夫をしてます。
- コンテンツが少ないときも
overscroll-behavior
を効かせるためにmin-height
を指定して微妙にスクロールできるようにします。 overscroll-behavior-y:contain
があると Safari では pull to refresh できなくなってしまいます。キーボードを閉じているときは pull to refresh できるようにoverscroll-behavior-y:contain
はキーボードが開いている間だけ有効にします。maximum-scale=1
でツールバーを非表示
機能を使ったタイミングで微妙に拡大されてしまうのを防ぎます。指定していても最近のブラウザではピンチアウトで拡大できると思います…- キーボードを開いた時にカーソルがヘッダやフッタの下に隠れてしまうことがあるので調整が必要ですが、少し頑張ればなんとかなるでしょう。
この方法でも実現できるUIに制限があり現実的にはある程度妥協することになりそうです。Safari が interactive-widget=resizes-content
に対応してくれるとかなり解決する気がするので、なんとかお願いします。