jQueryのdelegateとtap-highlightの悩ましい関係
こんにちは、しもだです。
jQueryでイベントハンドラをDOM要素にバインドする場合、bind(), live(), delegate()と
いくつかの方法があるのはみなさんご存じと思います。
おおざっぱにいうと、
- bind()は対象要素に直接addEventListener()するのとほぼ同等、
イベント発火⇒ハンドラ実行 - live/delegateはイベントのバブリングを拾って
セレクタのマッチングを行いながら(マッチしたら)ハンドラ実行
という動きをします。
# 注: jQuery1.7からはon(), off()ができたため、関数名的には同じになってしまいました。
DOM要素が動的に追加されるような場合、あるいは対象要素が多い場合には
delegate()が大変重宝しますが、これをスマートフォン/タブレットで使うと
条件により少し困ったことが起こります…というのが今日のお話です。
iPhoneやAndroidでリンクやボタンを押すと、グレー/オレンジ/青などの影がタッチした要素につきます。
# 正式な名前は知らないのですが、この表示を制御するCSSのプロパティが-webkit-tap-highlight-colorなので
# 以下tap-highlight(or ハイライト)ということにします
…そうです、delegate()を使うと、このtap-highlightが実際の対象要素でなく
delegate()でイベントハンドラを仕掛けている要素についてしまうことがあるのです。
というわけで、手元にある端末で試してみました。
<div id="container"> <div id="innerContainer"> <h2>h2-title</h2> <a href=".">a-link</a> <input type="button" value="button" /> </div> </div>
というHTMLと
var delegateTestController = { __name: 'DelegateTestController', 'a,h2,input click': function(context) { alert('click!'); //context.event.preventDefault(); }, }; h5.core.controller('#container', delegateTestController);
というJSを用意し、<a>, <input>,<h2>のclickイベントをdelegate()で拾ってみました。
使用したjQueryは1.7.2です。
下の表が結果です。それぞれ、どの要素のおしたときにどの要素にハイライトがつくかを示しています。
OS | 機種 | ブラウザ | <a>タグの場合 | <input>タグの場合 | <h2>タグの場合 |
iOS 5.1.1 | iPhone4 | Mobile Safari | <a> | <input> | delegate()している<div> (注1) |
iOS 5.1.1 | iPad(2012) | Mobile Safari | <a> | <input> | delegate()している<div> (注2) |
Android 3.1 | Optimus Pad (L-06C) | 標準ブラウザ | <a> | <input> | ハイライトなし |
Android 4.0.2 | Galaxy Nexus (SC-04D) | 標準ブラウザ | <a> | <input> | ハイライトなし |
Android 4.0.2 | Galaxy Nexus | Chrome for Android (0.18.4531.3636) |
<a> | <input> | delegate()している<div> (注1) |
Android 4.0.2 | Galaxy Nexus | Firefox (10.0.5) | <a> | <input> | ハイライトなし |
Android 4.0.2 | Galaxy Nexus | Opera Mobile (12.0.3) | <a> | <input> | delegate()している<div> |
WindowsPhone 7.5 (7.10.8107.79) |
IS12T | Internet Explorer9 | <a> | <input> | delegate()している<div>(注3) |
- 注1:<div>が画面内に完全に収まっている場合のみハイライトがつく
- 注2:<div>が「横方向に」収まっている場合のみハイライトがつく
(縦がはみ出しているかどうかは関係ない) - 注3:<div>の大きさ(?)によってついたりつかなかったりする。
画面内に<div>が収まっているときでもハイライトされないときもあった。
ただし、画面内に<div>が収まっていないときはハイライトされることはなかった
というわけで、概ね
- delegateの親要素が画面内にすべておさまっている
- <a>や<input>など、一般的に触る要素”以外”の要素のmouse系イベント(click, mouse*)を拾う
場合にdelegate()している親要素にハイライトがつく、ということがわかりました。
ちなみに、preventDefault()は効果なしでした。
また、touch*イベントの場合、たまに一瞬ハイライトがでる時があるものの、
繰り返しタッチするとほとんどの場合はでませんでした。…<a>タグでも出なくなりますが。
さて、ではどう回避するか、ですが・・・
- -webkit-tap-highlight-colorをtransparentにする
- (1の変形)delegate()のバインド先のtap-highlight-colorをtransparentにした上で
直下に<div>を置き、この<div>のtap-highlight-colorを適当なものにする。
中身はこの<div>の下に置く
※tap-highlight-colorは子要素に継承されるので、1だと<a>などすべての要素で
ハイライトがつかなくなってしまう -
(さらに変形)問題になるハンドラの中で一時的にtap-highlightをtransparentにし、後ですぐに戻す(うまくいくか未確認)
- セレクタで指定する要素を<a>, <input>などに限定する
- <a>,<input>系以外の要素ではtouch*イベントを拾うようにする
- delegate()をやめてすべてbind()にする
- (あきらめる、OSのバージョンが上がって状況がかわるのを待つ)
といったあたりでしょうか。
# 何か良い方法をご存じの方がいましたら是非コメントで教えてください…
まず、1だとフィードバックが全くなくなってしまいます。
余計なフィードバックをつけたくない!という場合にはむしろ好都合かもしれませんが…。
また、OperaMobileやWindowsPhoneでは解決しません。
2だと(iOS/Androidでは)表面上は大体解決するものの、
OperaMobileやWindowsPhoneでは問題が残ったままだし、余計な要素が必要になります。
3だとラッパーdivは不要になりますが、段々話が複雑になってきます。
場合によっては4で逃げ切れるかもしれません。
ただし、<div>を持ち上げてドラッグしたい…みたいな話が出てくると、やっぱり問題化します。
5はそこそこありかな、と思っています。ただし、PCとスマートフォンで
イベントの使い分けが必要になります。
6はもともとdelegate()を使いたい(動的に要素が増えるような)シチュエーションではイマイチ。
7にも期待したいところですが、なにせブラウザの挙動に関する部分なので
いつ何がどう変わるかわかりません。
4を起点にしつつ、仕方ないときは5, 6, 2あたりの手段を併用するか…といったところでしょうか。
悩ましいです。
コメントは受け付けていません。