Javascript中mouseover/mouseout事件冒泡停止失效解决办法

大家在写javascript交互脚本的时候最常用到的应该就是事件了,在处理事件的时候需要值得注意的是事件流的问题,事件流有三种模型:MSIE的冒泡模型,Natscape Navigator 的捕获型事件流,以及W3C标准中的同时支持捕获和冒泡两种模型。大部分现代浏览器都支持W3C的标准事件流模型,遗憾的是MSIE仍然只支持自己的冒泡模型。我们这里讨论的不是事件流的模型,这里只是简单的提一下,这篇文章要讨论的是mouseover和mouseout在停止冒泡或者说是停止派发事件后仍然继续触发的问题,也就是事件冒泡停止失效问题(这个名字是我自己起的,我也不是很清楚为什么会失效,是本身就这么设计的还是小bug?)。

我们在写下拉菜单或者小提示等效果的时候会用到mouseover和mouseout事件来作为事件触发的条件,但是如果我们用做触发的元素内部有其他的元素的时候当鼠标移上的时候会反复的触发mouseover和mouseout事件。点击mouseoverout测试1,当你的鼠标在红框与绿框之间移动的时候你会看见mouseover和mouseout事件疯狂的在触发。为什么会这样呢?因为内部的Div在鼠标移上的时候会向它的父对象派发事件,所以外面的框相当于也触发了mouseover事件。

而我们要实现的是当鼠标移动到红框(parent div)内部时候,只要鼠标不移动出框就只触发一次mouseover,同样只有鼠标移出最外面的框的时候才触发mouseout,这时,你突然想起来了,我们可以用event.cancelBubble=ture(MSIE)或者event.stopPropagation()(W3C)来停止事件的派发,然后我们兴冲冲地去做,但是迎接我们的只会是一盆冷水,这里我就不提供测试代码了,大家可以自己测试。

下面来看我们要实现的目标:

1,在鼠标移入对象时以及移入之后,不管内部有多少别的元素,只要鼠标在其内部移动,我们希望只触发一次mouseover事件。

2,当鼠标在对象内部时,即使移动到内部的其他元素上也不触发mouseout事件,只有在移出对象时才触发mouseout事件。

我们的目标很简单,大家来看最终我们要实现的效果,点击mouseOverOutTest2查看效果。在这个测试中红色的Mouse Hovered!和绿色的Mouse Outted!是我们需要的触发结果,其他的都是辅助信息。通过测试可以明确的看到我们达到了我们需要实现的目标。

下面我们就开始分析实现的方法。

既然我们没法通过停止事件派发来阻止mouseover和mouseout的反复触发,我们只好自己来在事件内部判断我们是否触发我们要做的操作。

这里要用到event对象的一个属性relatedTarget,这个属性就是用来判断mouseover和mouseout事件目标节点的相关节点的属性,是不是有点绕,简单的来说就是当触发mouseover事件时,relatedTarget属性代表的就是鼠标刚刚离开的那个节点,当触发mouseout事件时它代表的是鼠标移向的那个对象。当然MSIE它又不支持这个属性了:-P,不过它有代替的属性,分别是fromElement和toElement,是不是有点累赘,不过我忍了,通过字面的意思大家应该很清楚这两个属性的意义,这里就不多解释,要是不清楚大家可以到网上搜一下相关的资料。

有了这个属性,我们就能够清楚的知道我们的鼠标是从哪个对象移过来,又是要移动到哪里去了。这样我们就能够通过判断这个相关联的对象是否在我们要触发事件的对象的内部,或者是不是就是这个对象本身。通过这个判断我们就能够合理的选择是否真的要触发事件。

这里我们还用到了一个用于检查一个对象是否包含在另外一个对象中的方法,contains方法。MSIE和FireFox分别提供了检查的方法,这里封装了一个函数。

  1. function contains(parentNode,childNode){
  2. o return parentNode.contains ? parentNode != childNode && parentNode.contains(childNode) : !!(parentNode.compareDocumentPosition(childNode) & 16);
  3. }

这个函数用于检查一个对象是否被包含在我们的触发对象里面。

下面就是我们的重点了,我封装了一个用于检查鼠标是否真正从外部移入或者移出对象的函数checkHover(e,target),这个函数需要传入当前的事件对象和目标对象。

  1. function checkHover(e,target){
  2. oif(getEvent(e).type=="mouseover")
  3. oreturn !contains(target,getEvent(e).relatedTarget||getEvent(e).fromElement) && !((getEvent(e).relatedTarget||getEvent(e).fromElement)===target);
  4. oelse
  5. o{
  6. oreturn !contains(target,getEvent(e).relatedTarget||getEvent(e).toElement) && !((getEvent(e).relatedTarget||getEvent(e).toElement)===target);
  7. o}
  8. o}
  9. function getEvent(e){
  10. o return e||window.event;
  11. }

函数里面用到的getEvent()函数用于在MSIE或者FF下返回一个可用的event对象,这里你可以自己封装成别的函数。

函数的逻辑很简单,首先判断事件的类型,这个主要是为了迁就MSIE,当是mouseover的时候relatedTarget在MSIE下应该是fromElement,而mouseout则应该返回toElement,当然在FF下面就好办了,都是同一个属性relatedTarget。首先判断我们的relatedTarget是否在目标对象的内部,如果是的话则直接返回假如果不在内部的话则判断是否是目标对象本身,如果是的话返回假,要是两种情况都不成立则返回真。至于在IE下relateTarget如果判断请参照测试实例2判断。

到这里我们的主要工作做完了,有了这个函数我们在进行编程的时候只要在mouseover或者mouseout事件内部先检查一下,再进行下一步操作就能轻松实现hover的效果。

  1. myElement.onmouseover=function(e){
  2. oif(checkHover(e,this))
  3. o{
  4. odo someting...
  5. o}
  6. }
  7. myElement.onmouseout=function(e){
  8. oif(checkHover(e,this))
  9. o{
  10. odo someting...
  11. o}
  12. }

使用起来就是这么简单。

很简单的一点东西被我说起就变得这么麻烦了,主要本人也是刚刚在摸索javascript,碰到了这个问题让我难受了很久,在cloudgamer的blog上也学到了很多东西,强烈推荐学习js处于初级阶段的童鞋到他的blog上转转,解决这个问题的办法就是受JavaScript 浮动定位提示效果这篇文章的启示做出来的。感谢大家支持,我会一直努力。

完整代码下载:mouseOverOutFix

转载请注明原文出处《Javascript中mouseover/mouseout事件冒泡停止失效解决办法》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。

14条回复 发表于 “Javascript中mouseover/mouseout事件冒泡停止失效解决办法”上

  1. DK说道:

    2010年伊始,呵呵,图个好兆头,先顶自己一个,希望新的一年能够有更多的收获!

    [回复]

  2. luyi670说道:

    大哥,你太帅了,学习了,正好用到

    [回复]

    DK 说:

    呵呵,能有帮助就好,欢迎常来访问~

    [回复]

  3. 80男孩说道:

    兄弟,不顶你不行

    [回复]

    DK 说:

    @80男孩, 感谢支持!

    [回复]

  4. 123说道:

    唉~~
    我国互联网上真正有价值的网站也就一些个人的博客了.
    其他站长就知道拷贝粘贴,搜索出来的资料都是同一篇文章,翻来覆去的转…
    博客很不错,加油~
    🙂

    [回复]

    DK 说:

    @123, 感谢您的支持!

    [回复]

  5. […] 在Javascript中mouseover/mouseout 事件冒泡停止失效解决办法中我们通过checkHover()方法解决在触发mouseover和mouseout事件的时候无法阻止事件分发的bug。 […]

  6. 机器说道:

    太感谢了 写的很详细
    今天搞了一天才发现是这个问题 郁闷啊

    [回复]

    DK 说:

    @机器, 非常高兴对你有所帮助,你可以看一下我写的另外一篇文章http://www.dklogs.net/?p=355,我用自定义事件解决这个问题,使用起来比较方便。

    [回复]

  7. 矮胖丑黑说道:

    parentNode.compareDocumentPosition is not a function???
    怎么办

    [回复]

    DK 说:

    @矮胖丑黑, 能具体描述一下吗?我在测试的时候没有这个错误啊,低版本的IE支持contains不支持此函数

    [回复]

  8. yadiv说道:

    这个方法还是有bug. realtedTarget和target是同级对象还是会bug。

    [回复]

  9. 崔现飞说道:

    哥 我没看懂!

    [回复]

我要评论