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

2008/03/11

【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というのもあるみたいですね。もしかするとわざわざ↑のようなことをしなくても、これでできるのかな?(試していませんが)。

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

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

覚書」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック


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

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

戻るリンク追加

カレンダー

2019年9月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          

ココログカレンダーPlus

検索


    • Web全体 サイト内
    • 蔵書のISBNを入力して下さい
    • はじめる前
      初級者向け
      上級者向け
      ブログ紹介
      結果を表示

最近のトラックバック

コメントリストツリー化

無料ブログはココログ