バグの概要
Appleのフォーラムに2022年に投稿されている現象です。
そのフォーラムの投稿に再現用のCodePenのリンクもあり、現在もiOS18.5、26.2で動作・再現しました。
https://codepen.io/arisaito/pen/WNzymjv
See the Pen touchstart/end cheker by arisaito (@arisaito) on CodePen.
このデモページでは下部の青色とピンク色の枠内をタッチすると、タッチしている間だけその上に四角が現れるよう、touchstartとtouchend(青枠) / pointerdownとpointerup(ピンク枠) を使って実装されています。複数の指で同時にタッチしても正しく動作しますが、問題は片方の指が画面を離れるのと同時にもう片方の指が画面に触れた場合に後者のtouchstartイベントが起こらないという点です。何度か試すと比較的かんたんに再現できると思います。
Androidでは起こりません。
筆者が追加検証した結果、この場合には touchstart が発生しないだけでなく、
-
pointerdownやclickといった他のタッチ操作と関連するイベントも同様に発火しない - その瞬間だけでなく、それ以降その指が関わる
touchend等のイベントも一切発火しない -
touchstartイベントが起こらなかった指でも次の同様の現象を引き起こすことができる- 例えば右手
touchend(通常)と左手touchstart(発火しない)が同時に起こったあと、その左手のtouchend(発火しない)と同時に右手をタッチした場合そのtouchstartもまた発火しない...
- 例えば右手
ということがわかりました。
もし特定のiOSバージョンで修正されているなど続報を知っている方がいらっしゃったら教えていただけると助かります!
workaround?
現状なさそうです。
AIに聞いたり同様の事例を検索したりするとpreventDefault()とかが関係ありそうな気がしてきますが、関係ありません。タッチ操作に対してイベントが発火しないことが問題なので、イベントのコールバック内でどうにかできるものではなさそうです。
また、touchend側のコールバックをどうにかしたら解決できるものでもなさそうです。そもそもtouchendイベントをlistenしなかったとしても発生しました。
筆者が開発しているアプリでの例
筆者は Falling Nikochan (GitHub: na-trium-144/falling-nikochan) というブラウザで動く音楽ゲームを開発しています。
(もし興味を持っていただけましたらGitHubのスター・ YouTube:@nikochan144 や X:@nikochan144 のチャンネル登録/フォローをしていただけると嬉しいです!)
参考までに、この問題に対応する issue #523 があります。大したことは書いてません。
このゲームはPCではキーボードで、タブレット・スマホではタッチ操作でプレイすることができます。現状ではこのゲームに登場する音符はすべてキーを押した瞬間(keydown)およびタッチした瞬間(pointerdown)だけに判定があります。ちなみに最高評価の判定は±40msです。
2本以上の指でタッチする譜面も普通にあるので、運悪くpointerdownとpointerupが重なると判定されない場合があるというのは音ゲーとしては致命的でした。
また、音ゲーにしては比較的ライトユーザーをターゲットにしていることもあり、アナリティクスを見ると全アクセス数の約3分の1がiOSであるため、ユーザーへの影響もかなり大きいです。
暫定的な対策
苦肉の策として、その現象が発生した際にも発火している唯一のイベントであるpointerupを利用することにしました。
もしある指を離したタイミング(pointerup)が次の音符の判定タイミングとぴったり重なっており、かつその音符に対して次のpointerdownが検出されなかったのであれば、実はユーザーは正しくタップしているにも関わらずこのバグによりスルーされてしまっている可能性が存在することになります。
そのため、その場合には該当するpointerupのタイミングでタップされたものとみなして判定を行う、という仕様にしました。
デメリットとして、このゲームではタップの瞬間にSEが鳴るのですが、あとからpointerupのタイミングを参照して判定を行うこの実装であとからSEを鳴らすことはできません。そのためiOSでのプレイ時にはSE全体を無効にせざるを得ませんでした。
また、これをそのまま実装すると 本当にタップしていないものが誤って拾われる・この仕様を知ってしまえば簡単に悪用できてしまう、というのを避けたかったので、その場合には判定を厳しくする(具体的には±25msにする)ということでバランスを取りました。
それと、概要の3.に記載したようにこのバグが2回以上連続して起こった場合はtouchendもtouchstartも起こらないので反応できません。さすがにどうしようもない気がします。
(このような理由から、このゲームで精度狙いをする方にはiOSだけはおすすめしません。)