js mouseover mouseout 多次触发

问题:当鼠标移动到元素上,多次触发mouseover,mouseout事件。

(注,该问题是在实现鼠标移动到一起菜单,滑动弹出二级时碰到的;因为鼠标移动到二级菜单时,动画再次触发,才意识到该问题;之前因为使用的是:hover伪类实现的显示二级菜单,并且没有加入动画,所以并没有发现该问题。)

问题原因分析:事件的冒泡机制,当子元素上发生相应事件时,会触发父级元素的该事件。如A元素包含B元素,在A,B元素上分别添加mouseover,mouseout监听事件,当鼠标移到A上,但不在B上时,触发A的mouseover,同时对应的event.eventPhase为2即目标阶段;当鼠标继续移入B元素中时,这时触发A事件的mouseout,event.eventPhase为3,即冒泡阶段,同时触发B事件的mouseover,event.eventPhase为2,即目标阶段。

解决方法:检测事件的相关元素(relatedTarget,对于mouseover来说:relatedTarget是鼠标进入元素前,所离开的元素;对于mouseout,relatedTarget是鼠标离开元素后,所进入的元素)是被绑定元素的子元素与否。(或者利用jQuery的mouseenter,mouseleave事件,因为jquery已经将该事件封装)

function contains(parentNode, childNode) {
    if (parentNode.contains) {
        return parentNode != childNode && parentNode.contains(childNode);
    } else {
        return !!(parentNode.compareDocumentPosition(childNode) & 16);
    }
}

该函数是判断两个节点的关系,它考虑到IE与其他浏览器的兼容性,[dom].contains([dom])方法是IE浏览器的方法([dom]表示文档流中的节点),[A].compareDocumentPosition([B])是DOM3中的方法,下面是不同位置关系对应的返回结果。

 Bits          Number        Meaning
000000         0              元素一致
000001         1              节点在不同的文档(或者一个在文档之外)
000010         2              节点 B 在节点 A 之前
000100         4              节点 A 在节点 B 之前
001000         8              节点 B 包含节点 A
010000         16             节点 A 包含节点 B
100000         32             浏览器的私有使用

接下来是判断事件相关元素与目标元素之间的关系,只有当触发事件的相关元素不是目标元素的后继节点,checkHover()函数才返回true.

function checkHover(e,target){
    if (getEvent(e).type=="mouseover")  {
        return !contains(target,getEvent(e).relatedTarget||getEvent(e).fromElement) && !((getEvent(e).relatedTarget||getEvent(e).fromElement)===target);
    } else {
        return !contains(target,getEvent(e).relatedTarget||getEvent(e).toElement) && !((getEvent(e).relatedTarget||getEvent(e).toElement)===target);
    }
}

 

getEvent是为了兼容IE浏览器;checkHover函数中之所以添加一个if判断是因为IE下mouseover和mouseout的相关元素分别对应的是fromElement,toElement,因此分别处理,当是其他事件时,这两个属性在IE下为null。而FF和chrome浏览器中的相关元素都是relatedTarget,mouseover中relatedTarget是鼠标移到目标元素时所离开的那个元素,mouseout中relatedTarget是鼠标离开目标元素时要进入的元素,对于其他事件该属性无用。

最后是函数的调用。

myElement.onmouseover=function(e){
    if(checkHover(e,this)){
        do someting...
    }
}

 附加:

1)event还有两个对象currentTarget,target;currentTarget是当前响应事件的对象,target是最初触发该事件的对象;

2)阻止事件的冒泡为event.stopPropagation();

3)阻止默认行为为event.perventDefault();

4)在处理函数中最后写上return false;即阻止冒泡又阻止默认操作。

5)事件分为3个阶段:捕捉阶段,eventPhase为1;目标阶段,eventPhase为2;冒泡阶段,eventPhase为3。

下面为测试时,监控程序的代码

var count=0;
$(this).bind('mouseover',function(e){
        console.log(++count+' mouseover: '+e.target.className+" "+e.eventPhase);
        ...
    
}).bind('mouseout',function(e){
        console.log(++count+' mouseout: '+e.target.className+" "+e.eventPhase);
        ...
})

 

posted @ 2014-03-31 23:43  hugh.wei  阅读(4664)  评论(0编辑  收藏  举报