当前位置: 首页 > Web前端 > CSS

为什么mouseenter和mouseover这么纠结呢?

时间:2023-03-30 23:21:19 CSS

前言原文地址项目地址不知道大家在面试或者工作过程中有没有被mouseover和mouseenter(对应mouseout和mouseleave)事件所困扰。之前面试的时候被问到mouseover和mouseenter事件的异同点?我当时没有回答,对这两件事我一直有些模糊。目前正在阅读zepto的源码,准备写一篇这方面的文章。如有错误,请指正。mouseenter和mouseover有什么异同?要弄清楚mouseenter和mouseover的区别,或许可以从两个方面来谈。是否支持定时触发冒泡事件先来看一张图,对这两个事件有个简单直观的感受。看官网mouseenter的解释mouseenter|onmouseenterevent.aspx)只有当鼠标指针超出对象边界并且用户将鼠标指针移动到对象边界内时才会触发该事件。如果鼠标指针当前在对象边界内,为了触发事件,用户必须将鼠标指针移到对象边界外,然后再移回对象边界内。大概意思是:当鼠标从元素边界外移入元素边界内时触发事件。但是,当鼠标本身在元素的边界内时,要触发该事件,必须先将鼠标移出元素的边界,然后再次移入才会触发。(英文马马虎虎?我们来看看)和onmouseover事件不同的是,onmouseenter事件是不冒泡的。大概意思是:与mouseover不同,mouseenter不支持事件冒泡(英文是马虎的吗?我们来看看)因为不支持mouseenter事件冒泡,导致进入或离开元素的子元素时会触发mouseover和mouseout事件,但是mouseenter并且不会触发mouseleave事件。下面我们用一个动画来看看它们的区别(或者点击链接体验)。我们分别在左右ul添加了mouseover和mouseenter事件。当鼠标进入左右ul时,会触发mouseover和mouseenter事件,但是移动各自的子元素li时,会触发左侧ul上的mouseover事件,但是右侧ul上的mouseenter事件没有被触发。出现上述现象本质上是mouseenter事件不支持冒泡导致的。如何模拟mouseenter事件。可以看出,mouseover事件由于其冒泡特性,在子元素内移动时会频繁触发。如果我们不想这样,我们可以使用mouseenter事件来代替,但是早期只有ie浏览器支持这个事件,虽然现在大部分高级浏览器都支持mouseenter事件,但是难免会有一些兼容性问题,如果能自己手动模拟就好了。关键因素:relatedTarget如果想手动模拟mouseenter事件,需要了解触发mouseover事件时事件对象的relatedTarget事件属性。relatedTarget事件属性返回与事件的目标节点相关的节点。对于鼠标悬停事件,此属性是鼠标指针移到目标节点上时离开的节点。对于mouseout事件,此属性是鼠标指针离开目标时进入的节点。对于其他类型的事件,此属性没有用处。重新看文章原图,根据上面的解释,对于ul上添加的mouseover事件,relatedTarget只能是ul的父元素wrap(移入ul时,这也是触发mouseenter事件的时候,其实,不是一定的,后面会解释),或者ul元素本身(在其子元素上移出时),或者子元素本身(直接从子元素A移到子元素B)。根据上面的描述,我们可以判断relatedTarget的值:如果该值不是目标元素,也不是目标元素的子元素,则说明鼠标已经移动到目标元素中,而不是移动到目标元素内部元素。条件一:不是目标元素,容易判断e.relatedTarget!==target(targetelement)条件二:不是目标元素的子元素,这个应该怎么判断?ele.contains这里需要引入一个新的api[node.contains(otherNode)](https://developer.mozilla.org...表示传入的节点是否是本节点的后代节点,如果otherNode是node节点或节点节点本身。返回真,否则返回假使用情况1

  • 2
  • let$list=document.querySelector('.list')let$item=document.querySelector('.item')let$test=document.querySelector('.test')$list.contains($item)//true$list.contains($test)//false$list.contains($list)//true那么我们可以通过containsapi轻松验证条件2,然后我们封装了一个contains(parent,node)函数,专门用来判断node是否是parent的子节点letcontains=function(parent,node){returnparent!==node&&parent.contains(node)}我们封装后的contains函数并尝试上面的例子contains($list,$item)//truecontains($list,$test)//falsecontains($list,$list)//false(主要区别就在这里)这个方法在模拟mouseenter事件中很方便的帮我们解决了条件2,但是可悲的ode.contains(otherNode),具有浏览器兼容性,在某些低级别浏览器中不支持。为了兼容,我们重写contains方法letcontains=docEle.contains?function(parent,node){returnparent!==node&&parent.contains(node)}:function(parent,node){letresult=parent!==nodeif(!result){//从中排除父节点和节点传入同一个节点返回结果}if(result){while(node&&(node=node.parentNode)){if(parent===node){returntrue}}}returnfalse}这么说much,我们看一下最终用mouseover事件模拟mouseenter的代码//回调表示如果mouseenter事件执行时传入的回调函数letemulateEnterOrLeave=function(callback){returnfunction(e){letrelatedTarget=e.relatedTargetif(relatedTarget!==this&&!contains(this,relatedTarget)){callback.apply(this,arguments)}}}模拟mouseenter和原生mouseenter事件比较htmlwrap,mouseenter计数:
  • 1
  • 2
  • 3
  • wrap,emulatemouseenter,用mouseover模拟现实mouseentercount:
  • 1
  • 2
  • 3
  • css.wrap{宽度:50%;框大小:边框框;float:left;}.wrap,.list{border:solid1pxgreen;填充:30px;margin:30px0;}.list{border:solid1pxred;}.listli{border:solid1pxblue;填充:10px;margin:10px;}.count{color:red;}javascriptlet$mouseenter=document.querySelector('.mouseenter')let$emulateMouseenter=document.querySelector('.emulate-mouseenter')let$enterCount=document.querySelector('.mouseenter.count')let$emulateMouseenterCounter=document.querySelector('.emulate-mouseenter.count')letaddCount=function(ele,start){returnfunction(){ele.innerHTML=++start}}letdocEle=document.documentElement让包含=docEle.contains?function(parent,node){returnparent!==node&&parent.contains(node)}:function(parent,node){letresult=parent!==nodeif(!result){返回结果}if(result){while(node&&(node=node.parentNode)){if(parent===node){returntrue}}}returnfalse}letemulateMouseenterCallback=addCount($emulateMouseenterCounter,0)letemulateEnterOrLeave=function(回调){returnfunction(e){letrelatedTarget=e.relatedTargetif(relatedTarget!==this&&!contains(this,relatedTarget)){callback.apply(this,arguments)}}}$mouseenter.addEventListener('mouseenter',addCount($enterCount,0),false)$emulateMouseenter.addEventListener('mouseover',emulateEnterOrLeave(emulateMouseenterCallback),false)效果预览详细代码点击代码示例点击确定,我们已经通过mouseove事件mouseenter事件完全模拟,但是回过头来看,对于ul上添加的mouseover事件,relatedTarget只能是ul的父元素wrap(当ul移进来的时候,也是此时触发mouseenter事件的时候,但不一定,后面会解释),或者ul元素本身(当它被移出它的子元素),或者子元素本身(直接从子元素A移到子元素B),我们检查2和3,最后只剩下1,也就是当mouseenter和mouseover事件一起触发。既然如此,我们为什么不这样判断呢?target.addEventListener('mouseover',function(e){if(e.relatedTarget===this.parentNode){//在mouseenter回调中做什么}},false)这样是不是更简单了?,为什么还要检查2和3?原因是当target的父元素有一定的空间时,我们这样写问题不大,但是相反,此时e.relatedTarget可能就是target元素的父元素,而一些祖先元素之一。我们无法准确判断e.relatedTarget是哪个元素。所以排除2和3应该是更好的选择。使用mouseout模拟mouseleave事件。当mouseout被激活时,relatedTarget指示鼠标离开目标元素时进入了哪个元素。我们还可以判断relatedTarget的值:如果该值不是目标元素或目标元素的子元素,则说明鼠标已经移出目标元素。我们也可以使用上面封装的函数来完成。//callback意思是如果mouseenter事件执行了,传入的回调函数relatedTarget)){callback.apply(this,arguments)}}}详细代码点击代码示例点击结束本文可能有些观点不够严谨,欢迎大家分享。原地址项目地址