Just another Critical Works weblog

こんにちは。のぐちです。

先日、このブログの存在が社内に告知されました。
開始からしばらくはひっそりと生きようということで特に教えていなかったのです。

で、早速というか何というか、社内の開発者からの応援メッセージ(ツッコミ)が届きました。

そのひとつが
「何故jQueryを使ってるのに、addEventListener してるのか?」
でした。

確かにわざわざ次のように書きました。

box.addEventListener(“touchmove”, touchHandler, false);

僕も最初はjQueryを使って

$(“#box”).bind(“touchstart”, touchHandler);

なんて書いたのですが、タッチしても全然反応がないので、
「ああ、touch系のイベントはjQueryが対応してないんだろうな」
と思い込んでいました。

が、このツッコミを機にjQueryのコードを追ったところ、問題は少し違うことが分かりました。

結論として、touch系のイベントもjQueryの bind で問題なくバインドできるのです。

では、以下に調査の詳細を。

■ jQuery側のbindを調べてみました

bind関数は、基本的に jQuery.evetn.add 関数を呼び出します。(3行目)

bind: function( type, data, fn ) {
	return type == "unload" ? this.one(type, data, fn) : this.each(function(){
		jQuery.event.add( this, type, fn || data, fn && data );
	});
},

jQuery.event.add関数はちょっと長いので、一部だけ抜粋しますが、最終的には普通に addEventListener をしています。(4行目)

if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
	// Bind the global event handler to the element
	if (elem.addEventListener)
		elem.addEventListener(type, handle, false);
	else if (elem.attachEvent)
		elem.attachEvent("on" + type, handle);
}

(ちなみに今回調査に使ったのは jquery-1.3.2 です)

同じ addEventListener を使ってるのに、何故 jQuery 経由でバインドすると実行されないのか。

とてもとても悩みました。
jQuery が addEventListener を上書きしてるんじゃないかと疑ったほどです。

ここで、「あれ?待てよ」と、何かが頭をよぎりました。

おもむろにイベントにセットしているtouchHandler関数の冒頭にalert文を仕込んでみました。(touchHandler関数については、前回の記事の最後の方の flick.js を参照してください)

そしてiPhoneでタッチしてみたら、、、なんと、alert文が表示されるではないですか。

ええ、イベントは実行されていたんですね。
完全に思い込みでした。

つまり、エラーはtouchHandler内で起きている、ということです。
死んだ方がいいですね。

■ iPhone Safariでのデバッグ

恥ずかしながら打ち明けると、上述のjQueryの調査は、alert文を仕込みながらコツコツ調べました。

そして、ここに来て、いくらなんでも面倒だとやっと気づきまして、どうにか楽にできないものかと考えたときに、ふと思い出したんです。

iPhoneのSafariにもデバッグ機能があったことを・・・。

設定 > Safari > デベロッパ > デバッグコンソール
をONにすれば、スクリプトエラーが見られるのです。

早速試したところ、下記のようにエラーが出ました。

デバッグ結果

e.touches が undefind ですか。そうですか。

jQueryのソース調べなくても、一瞬で原因特定できましたね。。。
そろそろ本気で死んだ方がいいですね。

■ touches は何処へ

alert(e.type);

なんてやってみると、ちゃんと touchstart などと表示されるので、
touchHandler にはちゃんと引数としてイベントは渡ってきているようです。

なのに何故 touches はないんでしょう。

次のような処理を書いて、渡されたイベントのプロパティ名を一覧表示してみました。

var props = new Array();
for (var key in e) {
    props.push(key);
}
props.sort();
$("#msg").html(props.join("<br />")).css("text-align", "left");

引数eで渡されたイベントが持つすべてのkeyを配列に入れて、最後に改行コードでjoinしてメッセージエリアに表示しています。(メッセージエリアの元々のtext-alignがcenterで見にくいので、強引にleftにしています)

イベントのプロパティ一覧

ざっと見ると、originalEvent がそれっぽいような感じです。

■ originalEventで試してみる

では、touchHandler の下記の部分を試しに変更してみます。

// 変更前
var touch = e.touches[0];
// 変更後
var touch = e.originalEvent.touches[0];

するとなんと、うまく行きました!

つまり、jQueryでイベントをバインドした場合、touches は event.originalEvent.touches で取得しなければならない
ということです。

■ scriptの書き換え

では、うまくいったので、flick.jsの冒頭の部分もちゃんと書き換えます。

// 変更前
$(function() {
	var box = $("#box")[0];
	var touchHandler = getTouchHandler();
	box.addEventListener("touchmove", touchHandler, false);
	box.addEventListener("touchstart", touchHandler, false);
	box.addEventListener("touchend", touchHandler, false);

	box.addEventListener("webkitAnimationEnd", finishAnimation, false);
	box.addEventListener("webkitTransitionEnd", finishTransition, false);
});
// 変更後
$(function() {
    var touchHandler = getTouchHandler();
    $("#box").bind("touchmove touchstart touchend", touchHandler)
                 .bind("webkitAnimationEnd", finishAnimation)
                 .bind("webkitTransitionEnd", finishTransition);
});

これで無事に jQuery の bind を使ってイベントを設定できました。スッキリしました。
めでたし、めでたしです。

■ ところでoriginalEventって…

さて、いきなり一発目でtouchesの在処を見つけたので、「事件解決ですな」といった感じで、現場で負傷した捜査員のことなど気にも留めずに帰ってしまいそうなところですが、技術系ブログなら調べなくてはいけないですね。

が、疲れてしまいました。

とりあえず、originalEventのプロパティ一覧を出したところで今回は終わりにします。

更に調べるかどうかは、曖昧にしておきます。

originalEventのプロパティ一覧


(ちゃんと touches がいますね。)

コメント

コメント(1) “iPhone/SafariでjQueryを使ったイベントのバインドにおける注意点”

  1. [...] という形でli要素内の任意の子要素を取得していました。 ところが、beta 1 でclickイベントをvclickイベントに変更すると、上の方法ではクリックされたliを取得できませんでした。 公式ブログやらjQuery Mobileのソースやらを読んでみると、vclickはマウスのクリックイベントとタッチパネルデバイスのタップイベントやらを統合していい感じに処理してくれるカスタムイベントのようです。 で、取得したイベントの中の要素をここを参考にしてこんな感じで全部見てみました。 [...]

My Twitter