Follow-Up 2006-7-6
Firefoxでもwindow.eventを有効にするやり方のエントリーは
こちら
Gecko系のブラウザでは、これはNetscape4.x時代からずっとそうなんだけど、イベントは、イベントハンドラーにargumentとして渡されるので、この手の記述には必ず引数(ひきすう)を書く必要がある。一方、IEでは(Operaもか?)、イベントモデルには、最後に生じたイベントをキャプチャーするwindow.eventという属性があり、これはどこからでも自由にアクセスすることが出来る。以上のことをまとめると、イベントに関してクロスブラウザーな記述をしようと思えば、例えば以下のような感じになる。
function getEventType(e){
var e = e || window.event;
alert("Event Type is:" + e.type);
}
で、ここで引数に使用している"e" であるが、これはGecko系のためだけに必須である。window.event であれば、この引数は不要だ。で、自分は常々、この"e"を追放できないかなと思っていた。何しろ、イベントを絡ませるような関数に必ずついて回るからだ。スクリプトの記述はシンプルなのが良いのに、この"e" のために余計に引数が増えてしまう。
ppBlogでは数箇所に、この"e" が出てくるので、こいつを追放してやろうと決心した。そして色々弄った結果、これならOKかなというのを見つけたのでメモしておく。もちろん、ppBlogにも反映される。
まず、最初にやったことは、ppBlogのjs/lib.jsに記述してある、ページ内でのマウスイベントX座標をゲットする以下の関数において、Firefoxでイベントがどんな風に呼び出されるのかだ。
function getEventPageX(e){
var e = e ||window.event;
if(e.pageX){
return e.pageX;
} else if(e.clientX){
if(d.documentElement && typeof d.documentElement.scrollLeft != "undefined"){
return d.documentElement.scrollLeft + e.clientX;
} else if(d.body && typeof d.body.scrollLeft != "undefined"){
return d.body.scrollLeft + e.clientX;
}
}
return 0;
}
実行している関数自身は、arguments.calleeで参照出来る。そして、この関数が何によって呼び出されているかは、callerプロパティーを参照すればいい。これらを使って、上の関数に、
alert(arguments.callee.caller);
を付け足してみると、Firefoxでは、
function onclick(event){
getEventPageX(event);
}
というのが返ってくる。→デモ1。
ほほう。ちなみにIEでは
function anonymous()
{
getEventPageX(event);
}
という匿名関数が返ってくる。Firefoxでは、引数のeventがonclick関数によってキャプチャーされていることが分かる。じゃ、このonclick関数のeventって何だろう。これは次のようにして調べることが出来る。
alert(arguments.callee.caller.arguments[0]);
すると次の答えが返ってくる。→デモ2
[object MouseEvent]
ほうほう。イベントは、マウスイベントオブジェクトとしてonclick関数に組み込まれているらしい。ならば、これを呼び出して、"e" に渡してやれば、"e" の束縛から逃れられるのではと推察する。なので次のようにして、"e" なしのやつを走らせてみる。→デモ3
function getEventPageX(){
var e = arguments.callee.caller.arguments[0] || window.event;
if(e.pageX){
return e.pageX;
} else if(e.clientX){
if(d.documentElement && typeof d.documentElement.scrollLeft != "undefined"){
return d.documentElement.scrollLeft + e.clientX;
} else if(d.body && typeof d.body.scrollLeft != "undefined"){
return d.body.scrollLeft + e.clientX;
}
}
return 0;
}
おおぉー、上手く行った
3つ目のデモでは、もはや引数のeは必要なく、getEventPageX()だけで座標を取得していることに着目。
こりゃいいやと、js/lib.jsやjs/editor.jsに出てくる引数"e"をとりあえず、全部なしにしてブロッグを見てみると、意図したように動かない。何でかなと、デバッグダイアログを見てみると、これまでのデモでは、マウスクリックによって直接getEventPageX()を呼び出していたのに対して、実際のスクリプトでは、getEventPageX()などは、別の関数の中で呼び出されている。なので、
arguments.callee.caller.arguments[0]
だと、getEventPageX()を含んだ関数の第一引数を返してしまうのである。初歩的ミス。。それならば、
arguments.callee.caller
を次々に呼び出していけば、そのうち、マウスイベントオブジェクトに到達するはずである。イメージ的には、sourceに向かって遡っていくイベントバブリングですね。これは次のような記述でいい。
var caller = arguments.callee.caller;
while(caller){
var ob = caller.arguments[0];
if(ob == '[object MouseEvent]') return ob;
caller = caller.caller; // 次の呼び出し元にセット
}
ただ、ob == "[object MouseEvent]" というのは何となく「カッコ悪い」。この部分はコンストラクタを用いて次のようにも書ける。
ob.constructor == MouseEvent
以上をまとめて、IEでも使えるようなクロスな記述にすると以下のような感じ。
function windowEvent(){
if(window.event) return window.event;
var caller = arguments.callee.caller;
while(caller){
var ob = caller.arguments[0];
if(ob && ob.constructor == MouseEvent) return ob;
caller = caller.caller;
}
return null;
}
windowEvent()がそのままIEのwindow.eventに相当する。これを用いればイベント関連の記述に必須であったeからめでたく解放される訳である。最初に出てきたgetEventPageX()関数は、最終的には次のように記述できる。
function getEventPageX(){
var e = windowEvent();
if(e.pageX){
return e.pageX;
} else if(e.clientX){
if(d.documentElement && typeof d.documentElement.scrollLeft != "undefined"){
return d.documentElement.scrollLeft + e.clientX;
} else if(d.body && typeof d.body.scrollLeft != "undefined"){
return d.body.scrollLeft + e.clientX;
}
}
return 0;
}
もはやイベントのキャプチャーのためだけに引数は必要ない。最終的なデモをあげておきます。 →http://p2b.jp/demos/windowEvent.html
なお、イベント座標取得関数とは別に、イベントが起こった要素の座標を返す、getElementPosition()という関数も新たに定義しています。これは、JavaScriptマスターであるppk氏
のサイトにある"Find Position
" という記事に紹介されている関数をmodifyしたやつです。こんなやつ。
function getElementPosition(){
var _x = _y = 0;
var ob = windowEvent().target || windowEvent().srcElement;
if(ob.offsetParent){
while (ob.offsetParent){
_x += ob.offsetLeft; _y += ob.offsetTop;
ob = ob.offsetParent;
}
}
return [_x, _y];
}
とりあえず 
Comments