2008年3月11日(火)

« 【ココログ】特定の記事に対する最近のコメントをRSSで取得(Yahoo!Pipes版) | トップページ | はてなダイアリーの脚注をその場で見るためのGreasemonkey/SeaHorse »

【JavaScript】onmouseover/onmouseoutの振る舞い

マウスイベントである onmouseover/onmouseout はそれぞれあるオブジェクトに対してマウスが重なった/外れた場合に発生するイベントですが、どうも動作が直感的じゃないなぁ、と感じていたので、少し調べてみました。
どういうことかというと、例えば、
A
B
C

のように、親オブジェクトAの中に子オブジェクトB、孫オブジェクトCがいるような場合。
Aに対してonmouseover/onmouseoutを設定したとすると、直感的には、
  1. Aの外から内側に入った場合にのみ、overイベントが発生。
  2. Aから外に出た場合にのみ、outイベントが発生。
という動作を期待してしまいますが、これは期待通りになりません。
というのは、A~B間、B~C間の境界を移動した場合にも、当該イベントが発生してしまうからです。
たとえば、A→Bに動くと、一旦 out が発生してから改めて over が発生します。
つまり、一旦領域外に出てから、改めて入ったかのように振る舞ってしまうわけです。
さらに、A・B・C全てに対してonmouseover/onmouseoutを設定した日には、かなりいやらしい動作となってしまいます。
B→Cへと移動した日には、"OUT-B OUT-A OVER-C OVER-B OVER-A"と、都合5回のイベントが発生します(w。
そこで、もう少し直感的な動作に近くなるように、onmouseout/onmouseover専用のイベントハンドラ登録関数を作成してみました。
動作サンプル

Mousehandler_2

サンプルソース
(function(){
var w=window,d=w.document;

w.setEventHandler=(function(){
    if (w.addEventListener) {
        return function(obj,evt,handler){obj.addEventListener(evt,handler,false)};
    }
    else if (w.attachEvent) {
        return function(obj,evt,handler){obj.attachEvent('on'+evt,handler)};
    }
    else {
        return function(obj,evt,handler){var org=obj['on'+evt];obj['on'+evt]=function(){if(typeof org=='function')org();handler()}};
    }
})();   //  end of setEventHandler()

w.setMouseHandler=function(obj,evt,handler) {
    var mouseHandler=function(curEvent,e) {
        if (!e) e=w.event;
        if (!e) return;
        var prop=obj._MouseProp_;
        var curStatus=prop.status;
        if (curStatus==curEvent) return;
        if (curStatus=='out') { //  outer to inner
            var fncArrayOver=prop.fncArrayOver;
            for (var ci=0,len=fncArrayOver.length; ci<len; ci++) {
                fncArrayOver[ci].apply(obj,[e]);
            }
        }
        else {                  //  inner to outer
            var chkNode=e.toElement||e.relatedTarget;
            while (chkNode) {
                if (chkNode==obj) return;
                chkNode=chkNode.parentNode;
            }
            var fncArrayOut=prop.fncArrayOut;
            for (var ci=0,len=fncArrayOut.length; ci<len; ci++) {
                fncArrayOut[ci].apply(obj,[e]);
            }
        }
        prop.status=curEvent;
    };
    var prop=obj._MouseProp_;
    if (!prop) {
        prop=obj._MouseProp_={status:'out',fncArrayOver:[],fncArrayOut:[]};
        w.setEventHandler(obj,'mouseover',function(e){mouseHandler('over',e)});
        w.setEventHandler(obj,'mouseout',function(e){mouseHandler('out',e)});
    }
    if (evt=='mouseover') {
        prop.fncArrayOver[prop.fncArrayOver.length]=handler;
    }
    else if (evt=='mouseout') {
        prop.fncArrayOut[prop.fncArrayOut.length]=handler;
    }
    else {
        w.setEventHandler(obj,evt,handler);
    }
};
})();
setEventHandler(object,event,handler)は、単にaddEventListener()やattachEvent()のラッパ関数です。
setMouseHandler(object,event,handler)の方が、mouseover/mouseout専用のイベントハンドラ登録用関数になります。
eventは'on'をとったものを使います('onmouseout'→'mouseout')。
作りとしては単純で、
  1. デフォルト(ハンドラ登録時)は"外"にいるものとして初期化。
  2. "外"において発生した'mouseover'により、"外"→"内"に移動したものとみなす。
  3. "内"にいるときに発生する'mouseover'は無視。
  4. "内"にいるときに'mouseout'が発生した場合、移動先のオブジェクトをevent.toElement(IE)もしくはevent.relatedTarget(Firefox他)で取得し、そこから親を辿っていって、イベントを設定したオブジェクトが見つからない場合にのみ、"外"へと移動したものとみなす。
としています。
で、例によって作ってから気付きましたが、IEの場合はonmouseenterやonmouseleaveというのもあるみたいですね。もしかするとわざわざ↑のようなことをしなくても、これでできるのかな?(試していませんが)。

この記事をニフティクリップβに追加 この記事をはてなブックマークに追加 2008/03/11(火) 01:57 | | 記事の編集(管理者用)

風柳へひとこと(web拍手) 

パソコン・インターネット」カテゴリの記事

覚書」カテゴリの記事

トラックバック

この記事のトラックバックURL:

記事との関連性が薄いものやSPAM等、管理人が不適切と見なしたトラックバックについては予告無く削除する場合が有ります。悪しからずご了承下さい。

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/161784/40460169

この記事へのトラックバック一覧です: 【JavaScript】onmouseover/onmouseoutの振る舞い:

コメント

コメントを書く