【JavaScript】onmouseover/onmouseoutの振る舞い
マウスイベントである onmouseover/onmouseout はそれぞれあるオブジェクトに対してマウスが重なった/外れた場合に発生するイベントですが、どうも動作が直感的じゃないなぁ、と感じていたので、少し調べてみました。
どういうことかというと、例えば、
のように、親オブジェクトAの中に子オブジェクトB、孫オブジェクトCがいるような場合。
Aに対してonmouseover/onmouseoutを設定したとすると、直感的には、
というのは、A~B間、B~C間の境界を移動した場合にも、当該イベントが発生してしまうからです。
たとえば、A→Bに動くと、一旦 out が発生してから改めて over が発生します。
つまり、一旦領域外に出てから、改めて入ったかのように振る舞ってしまうわけです。
さらに、A・B・C全てに対してonmouseover/onmouseoutを設定した日には、かなりいやらしい動作となってしまいます。
どういうことかというと、例えば、
A
B
C
のように、親オブジェクトAの中に子オブジェクトB、孫オブジェクトCがいるような場合。
Aに対してonmouseover/onmouseoutを設定したとすると、直感的には、
- Aの外から内側に入った場合にのみ、overイベントが発生。
- 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専用のイベントハンドラ登録関数を作成してみました。
■動作サンプル
■サンプルソース
setMouseHandler(object,event,handler)の方が、mouseover/mouseout専用のイベントハンドラ登録用関数になります。
■動作サンプル
■サンプルソース
(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()のラッパ関数です。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);
}
};
})();
setMouseHandler(object,event,handler)の方が、mouseover/mouseout専用のイベントハンドラ登録用関数になります。
eventは'on'をとったものを使います('onmouseout'→'mouseout')。
作りとしては単純で、
- デフォルト(ハンドラ登録時)は"外"にいるものとして初期化。
- "外"において発生した'mouseover'により、"外"→"内"に移動したものとみなす。
- "内"にいるときに発生する'mouseover'は無視。
- "内"にいるときに'mouseout'が発生した場合、移動先のオブジェクトをevent.toElement(IE)もしくはevent.relatedTarget(Firefox他)で取得し、そこから親を辿っていって、イベントを設定したオブジェクトが見つからない場合にのみ、"外"へと移動したものとみなす。
で、例によって作ってから気付きましたが、IEの場合はonmouseenterやonmouseleaveというのもあるみたいですね。もしかするとわざわざ↑のようなことをしなくても、これでできるのかな?(試していませんが)。
« 【ココログ】特定の記事に対する最近のコメントをRSSで取得(Yahoo!Pipes版) | トップページ | はてなダイアリーの脚注をその場で見るためのGreasemonkey/SeaHorse »
「パソコン・インターネット」カテゴリの記事
- Twitter 原寸びゅー:PC版ブラウザ用・Twitterの画像閲覧と保存がはかどる拡張機能の紹介(2016.02.12)
- スマートフォンをPC上の音楽を再生するためのリモコンとして使いたい(2016.01.10)
- BIGLOBE光ネクスト(大阪)の通信速度問題 - プロバイダ選びは難しい……(2015.08.13)
- BOOK☆WALKER さんに関して最近経験した不安と不満(2015.08.10)
- 『#鳥獣戯画制作キット』が楽しい(2015.07.01)
「覚書」カテゴリの記事
- 鍛高譚 ~ カレイにまつわる物語(2018.05.25)
- ココログをTwitterカードに対応させてみる(2016.11.23)
- 神使の兎 ~宇治神社にて~(2016.07.10)
- Twitter 原寸びゅー:PC版ブラウザ用・Twitterの画像閲覧と保存がはかどる拡張機能の紹介(2016.02.12)
- スマートフォンをPC上の音楽を再生するためのリモコンとして使いたい(2016.01.10)
« 【ココログ】特定の記事に対する最近のコメントをRSSで取得(Yahoo!Pipes版) | トップページ | はてなダイアリーの脚注をその場で見るためのGreasemonkey/SeaHorse »
コメント