iOS Safari で、タップ時に :hover や :active を有効にする方法 詳細調査
前回の記事で、「スマホで :hover
時のアニメーションを有効にする」方法について触れた。
タップしている間だけ
:hover
を機能させるにはどうしたらいいか調べたところ、<body ontouchstart="">
と書けば実現できるようだ。
コレで確かに実現できるし、問題は解決したのだが、どうして onTouchStart
イベントを空で作成すると :hover
が効くのか、:focus
や :active
はどうなる?など、疑問がいくつか湧いたので検証してみた。
目次
- 検証ページ
- onTouchStart イベントの設定方法
:hover
・:focus
・:active
の兼ね合い- なぜ onTouchStart に空関数を設定すると
:hover
や:active
が効くのか - 参考文献
検証ページ
以下に検証用のページを作った。iOS Safari で以下のページを表示させて確認した。
ソースコードは以下。
なお、検証した環境は以下のとおり。
- iPhone7Plus・iOS 11.4・Safari ブラウザ
onTouchStart イベントの設定方法
まずは onTouchStart
イベントの設定方法を試してみた。
<body ontouchstart="">
と、HTML 側に処理を書くのが嫌だったので、JavaScript コードに寄せられないか検証した。結論からいくと、以下のいずれの書き方でも問題なく、:hover
を有効にできた。
window.ontouchstart = function() {};
window.addEventListener('touchstart', function() {}, true);
window.addEventListener('touchstart', function() {}, false);
document.ontouchstart = function() {};
document.addEventListener('touchstart', function() {}, true);
document.addEventListener('touchstart', function() {}, false);
document.body.ontouchstart = function() {};
document.body.addEventListener('touchstart', function() {}, true);
document.body.addEventListener('touchstart', function() {}, false);
TouchStart イベントを設定さえすれば、window
でも document
でも、document.body
でも大丈夫だった。addEventListener
でも良いし、第3引数の useCapture
も影響しないようだ。
ココは window.addEventListener()
か document.addEventListener()
を利用して空の関数を渡しておけば、ページ全体で :hover
が効くようになる。
ちなみに、ココで追加するイベントは onTouchMove
や onTouchEnd
でも良いようだ。
:hover
・:focus
・:active
の兼ね合い
次に、:hover
・:focus
・:active
という3種類の擬似クラスがどのように動くか試してみた。
iOS Safari の場合は以下のようになった。
擬似クラス | デフォルト | onTouchStart 設定後 |
---|---|---|
:hover |
要素をタップして指を離すと有効になる | 要素をタップした瞬間に有効になり、別の要素にフォーカスが移るまで有効のまま |
:focus |
有効にならない | 有効にならない |
:active |
有効にならない | 要素をタップした直後に有効に有効になり、指を離すと効果が消える |
:focus
は有効にならなかった。:active
は、onTouchStart
に空関数を設定した後は、PC における :hover
っぽい感覚で効いてくれた。:hover
自体は一度タップしてフォーカスが残っている間はずっと効果が付いてしまう。
だから、例えば :hover
と :active
を両方設定すると、「タップする (TouchStart)」→「:active
有効化」→「指を離す (TouchEnd)」→「:active
無効化・:hover
有効化」という順に表示が切り替わる。
なぜ onTouchStart に空関数を設定すると :hover
や :active
が効くのか
さて、一番気になっていた「どうして」の部分だが、コレは調べてもそれらしい理由が出てこず分からなかった。
iOS magic.
「iOS magic」なんですか…。
:hover
イベントは「マウスがポインティングされている」時に発火するので、タッチした後に発火することは理解できる。しかし、どうして onTouchStart
なタイミングではなく、onTouchEnd
後なのだろうか?推測だが、onTouchStart
時点では、その後に onTouchMove
、つまり「ドラッグ」的な操作もされ得るので、ポインティングしたとは言い切れないのかと推測される。
つまり、「:hover
= onTouchStart
→ onTouchMove
→ onTouchEnd
」な順序が好ましいように感じていること自体がタッチデバイスに対しては誤りで、「ポインティングの確定」は onTouchEnd
の後というのが正なのだろう。しかし、人間にとってあまり直感的ではない、と。
- 参考 : Detecting touch: it's the 'why', not the 'how' - Mozilla Hacks - the Web developer blog … コレに近いイベントの実行順序が書かれている。
onTouchStart
・onTouchMove
・onTouchEnd
のいずれかに関数を指定することで、iOS がタッチ操作を区別するために待機する時間をキャンセルし、そのユーザ指定の関数を実行して (= 空だから何もせず即座に) 後続のイベントに移行するものと思われる。