问题:当鼠标移动到元素上,多次触发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.οnmοuseοver=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); ... })