iOSの闇 kyoto.js 18
こんにちは、shokaishokai.iconです
株式会社HelpfeelでScrapboxを開発しています
ScrapboxのエディタをiPad/iPhoneに対応させました
その時に遭遇したiOSの闇について、全てをお話します Scrapboxのスマホ・タブレット対応
2022年6月〜8月ごろ
3つを自由に組み合わせて使えるようにした
1. デバイス本体
スマホ(iOS・Android)
タブレット(iPad)
2. テキスト入力
スクリーンキーボード
物理キーボード
3. ポインタ操作
指で画面タッチ
トラックパッド・マウス
iPadからmagic keyboardを操作中に付けたり外したりしてタブレット単体で操作する等も、わりといい感じになりましたshokai.icon
タッチとクリックでUIの振る舞いが変わる様子
https://scrapbox.io/files/63dc8ded8e2c92002194c9bf.MOV
指でタッチするとカーソルの上にツールが出る
トラックパッドでクリックすると、出ない
指タッチで3アイテムのメニュー
トラックパッドでは、hoverで1アイテム、クリックで3アイテムのメニュー
実装方針
UserAgentでのデバイス判別は将来的に使えなくなるらしいので、使わない
typeof window.ontouchstart === 'function'なども、将来Macがスクリーンタッチ可能になったら困るので使わない
ブラウザやOSを判別してコンポーネント出し分けせず、デスクトップもモバイルも同じコンポーネントを出したい
iPadは物理キーボード付けたり外したりする
将来Macも画面をタッチするだろう
既にMicrosoft SurfaceやChromebookは、画面タッチとトラックパッドが共存し、スクリーンキーボードと物理キーボードも共存してる
1つのコンポーネントでタッチもクリックもスクリーンキーボードも物理キーボードも全部やるぞ!
という風になりたいshokai.icon
まだ完成してません
お知らせ
このページに貼られている開発サーバーのURLは、もう使われていない古い物です
心配しないでください
動画にモザイクかけるのがめんどくさくてサーバーのURLの方を変えた
画面の下側、スクリーンキーボードの真上にツールバーを設置したかった
https://scrapbox.io/files/63dcdb762a0d0e001d58c44f.png
のだが、操作しているとたまに消える
10〜16秒あたり
https://scrapbox.io/files/63dc867f7679c4001d95e192.MOV
https://scrapbox.io/files/62ebe715387a7e0021d543d9.jpeghttps://scrapbox.io/files/62ebe718cb087e0020e24382.jpeg
解決方法
あきらめた
上に設置した
https://gyazo.com/7f1efb92841f7aee535e5ccbc28c3610
本当は下の方が良いと思うshokai.icon
親指から近いので
画面の上側に要素をくっつけたい
code:css
.navbar {
position: fixed;
top: 0;
}
スクリーンキーボードが開いていると
navbarがスクロールについてこなくなる
https://scrapbox.io/files/62ebe60140499c001dffd027.MOV
解決方法
キーボードを開いている時だけ
window.addEventListener('scroll', fn)でwindow.scrollYを取得して
position: absolute;とtop: window.scrollYをセットする
iOSのスクリーンキーボードは、カーソルの左側に文字がないとbackspaceキー押しっぱなしにしてもイベントが1回しか発火しない backspace押しっぱなしで連続削除の実装に必要
https://scrapbox.io/files/63dc8727cbd198001d3d9543.MOV
webでエディタを自作する時
空のtext inputを浮かせておいて
キー入力イベントを受け取って
それで画面を作る、というのをやるのだが
空のtext inputでbackspaceキーを押しっぱなしにすると
keydownイベントが連続発火する環境
Mac、Windowの物理キーボード
Androidのスクリーンキーボード
iOS/iPadOSの物理キーボード
keydownイベントが1回しか発火しない環境
iOS/iPadOSのスクリーンキーボード
何が起きているのか?
指を離していないのに勝手にkeyupが発火する
https://scrapbox.io/files/63dc872f303aff001e561872.png
keydownしてわずか32 msec後にkeyupが勝手に発火している
解決方法
backspace押しっぱなしで連続削除するために
文字を削除されたら左側にダミーの文字を挿入し、カーソルをその右側に移動させた
code:js
onKeydown (e) {
if (getAction(e) === 'backspace') {
textarea.value = '\t'
textarea.setSelectionRange(1, 1)
}
スクリーンキーボードを閉じるボタンが消滅する
https://scrapbox.io/files/63dc873a9ea49b001e979a6d.pngがなくなって、キーボードが閉じれなくなる
https://scrapbox.io/files/63dc873d313c5e001d5c1fbe.pnghttps://scrapbox.io/files/63dc8741aa9518001d503ccc.png
画面を縦にしたり横にしたりを繰り返すと発生する
解決方法
キーボードを閉じるために、画面上に常にエディタではない余白を用意しておく
左右に余白を残しておいた
横幅いっぱいのエディタを作ろうとしてる人は注意
iOSやiPadOSに物理キーボードを付けてcontrol + iを押すと、タブ文字が入力される
iOSはcontrol + iで文字が入力される
Mac/Windows/Androidでは、control + iを押しても文字は入力されない
Scrapboxでは、control + iを自分のアイコンを入力するショートカットキーとして使っているので困る
その時のkeydownイベントを見てみると
https://gyazo.com/a5ddefec67c4e89a663ef0eb0b623ab4
event.keyはiなのに
event.keyCodeは\tになっている
iOSやMacはcontrol + a,e,b,f,n,p,k,yなどのemacsキーバインドが使える
解決方法
ショートカットキー作る時に気を付ける
keyがiなのにkeyCodeが\tのイベント、で判別できる
iOSに物理キーボードを接続すると、フォーカスが2つになって別々のUIを操作しはじめる
iPhone + 物理キーボードで発生する問題です
iPad + 物理キーボードでは問題なし
たまにブラウザ全体に青い枠が付く
https://scrapbox.io/files/63a181e28696e7001dbce578.png
文字入力はできるのに、上下左右キーが効かなくなる
control + a,e,b,f,n,pは効く
emacsキーバインドでのカーソル移動
発生条件
いまいちよくわからない。突然発生するshokai.icon
ネイティブアプリでも発生する
https://scrapbox.io/files/63dc8752ea447e001da0fd5a.mov
最初、2箇所にフォーカスがある
上のhttps://scrapbox.io/files/63dc8757ccc144001ed7c7d9.pngと
下のテキスト入力欄
この状態では文字は入力できるが、上下左右キーでカーソルが移動できない
control - a,e,f,b,n,pでの移動は効く
tabキーを押すと<にあったフォーカスを移動できる
2つのフォーカスを合わせる事で、テキスト入力がちゃんとできるようになる!!
マウスもタップも使わず、キーボードだけで画面操作するための機能
そいつとテキスト入力のフォーカスが別々に存在している
解決方法
ない
ないけど、iPhoneにbluetoothキーボード接続してる人はたまにいる
なんかいい感じにサポート問い合わせ対応などをがんばる
iPhoneとiPadでスクリーンキーボードを閉じるボタンの挙動が違う
スマホユーザーは変換候補の選択ではなく、スクリーンキーボードを閉じる事で日本語入力の確定を行う事がある
スクリーンキーボードを閉じる方法
1. textarea外をタップする
2. 閉じるボタンを押す
iPhone / iPad
https://scrapbox.io/files/63a1811a565dff001e22b45a.pnghttps://scrapbox.io/files/63a1811ec373c9001edce58c.png
この2つは挙動が違う
キーボードの開閉に合わせてツールバーなどを表示・非表示するのが難しいshokai.icon
<textarea>に文字を入力して、スクリーンキーボードを閉じたらどうなるか
計測の準備
code:js
const textInput = document.querySelector('.text-input')
const events = [
'focus',
'blur',
'input',
'change',
'compositionstart',
'compositionupdate',
'compositionend'
]
for (const name of events) {
textInput.addEventListener(name, e => {
console.log(name, e.target.value)
})
}
「あ」を2回入力してから変換確定せずに、キーボードを閉じた時の挙動
キーボードが隠れつつ
iPad / iPhone
https://scrapbox.io/files/63a181110da26d001d6bf29b.png https://scrapbox.io/files/63a1810fabe473001db1fef5.png
ともにOSバージョン16.2のSafari
空のinputイベント
右だけchangeと、blur直後にfocusがある
iPad
https://scrapbox.io/files/63a181110da26d001d6bf29b.png
おおまかな順序
謎の空input
一回だけ、e.tarvet.valueが空のinputイベントが発火する
同じ現象はMac safariでも発生する
Mac chromeでは発生しない
iPhone
https://scrapbox.io/files/63a1810fabe473001db1fef5.png
おおまかな順序
change → blur → focus → 値のないinput → input → compositionend
閉じるボタンを押すとすぐblurするが、その後もう一度focusしている
スクリーンキーボードを閉じているが、focusが入ったままになっている
iPadでは、スクリーンキーボードを閉じるとfocusは外れた状態になる
changeが発火する
これはiPadでは発火しないイベント
iPhoneでは操作を終えると、最終的にfocusの青い枠がついたままになっている
iPadではこの青枠は現れない
https://scrapbox.io/files/63a18126953a0b0022b76382.png
しかし中にcaretは入っていない
この青い枠はキーボードナビゲーションのフォーカスだと思われるshokai.icon
解決方法
compositionstartからendまでの間は、空のinputイベントは無視する
changeイベントは使わないようにしよう
change使ってiPhoneで実装して、動いたーって思ってiPadで試すとchangeが発火しないので、困る
スマホ・タブレットでは、変換確定せずにスクリーンキーボード閉じたり、text area外をタップして入力完了したり、という操作をわりとやる
パソコンでもできるけど、習慣的にあまりやらない
エディタ作る人は気をつけてください
スクリーンキーボードの開閉に完璧に合わせてツールバーを出し入れするのは諦める
キーボード出てる時にツールバー出す、はできる
キーボード隠してるのにツールバー出っ放し、は回避できない
そういう状態がありえる事を前提にUIを作るしかない
おわり
iPhoneとiPadはぜんぜん別のデバイス
闇がいろいろある
でも、iOSはPWAがいつのまにかけっこういい感じになったりもしている タッチもクリックもするし、物理キーボードとスクリーンキーボード切り替えもどっちも実装しないといけないな、という納得ができた