jsmouseovermouseout多次触发
问题:当⿏标移动到元素上,多次触发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 (ains) {
return parentNode != childNode && ains(childNode);
} else {
return !!(parentNodepareDocumentPosition(childNode) & 16);
}
}
该函数是判断两个节点的关系,它考虑到IE与其他浏览器的兼容性,[dom].contains([dom])⽅法是IE浏览器的⽅法([dom]表⽰⽂档流中的节点),[A]pareDocumentPosition([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是⿏标离开⽬标元素时要进⼊的元素,对于其他事件该属性⽆⽤。
最后是函数的调⽤。
if(checkHover(e,this)){
}
}
附加:
1)event还有两个对象currentTarget,target;currentTarget是当前响应事件的对象,target是最初触发该事件的对象;
js获取子元素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);        ...
})