javascript中mouseenter与mouseover的异同


Posted in Javascript onJune 06, 2017

不知道大家在面试或者工作过程中有没有被 mouseovermouseenter (对应的是 mouseoutmouseleave )事件所困扰。自己之前在面试的时候就有被问到诸如mouseover和mouseenter事件的异同之类的问题?当时没有答出来,一直也对这两个事件有点模糊不清,趁着最近正在读 zepto源码 ,准备写一篇这方面的文章,如果有错误,请大家指正。

javascript中mouseenter与mouseover的异同 mouseenter与mouseover的异同?

要说清楚mouseenter与mouseover有什么不同,也许可以从两方面去讲。

是否支持冒泡 事件的触发时机

先来看一张图,对这两个事件有一个简单直观的感受。

javascript中mouseenter与mouseover的异同 再看看官网对mouseenter的解释

mouseenter | onmouseenter event

The event fires only if the mouse pointer is outside the boundaries of the object and the user moves the mouse pointer inside the boundaries of the object. If the mouse pointer is currently inside the boundaries of the object, for the event to fire, the user must move the mouse pointer outside the boundaries of the object and then back inside the boundaries of the object.

大概意思是说:当鼠标从元素的边界之外移入元素的边界之内时,事件被触发。而当鼠标本身在元素边界内时,要触发该事件,必须先将鼠标移出元素边界外,再次移入才能触发。(英语比较渣:no_mouth:,凑合看哈)

Unlike the onmouseover event, the onmouseenter event does not bubble.

大概意思是:和mouseover不同的是,mouseenter不支持事件冒泡 (英语比较渣:no_mouth:,凑合看哈)

由于mouseenter不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其mouseover和mouseout事件,但是却不会触发mouseenter和mouseleave事件

我们用一张动图来看看他们的区别(或者点击该链接体验)。

javascript中mouseenter与mouseover的异同

我们给左右两边的ul分别添加了 mouseovermouseenter 事件,当鼠标进入左右两边的ul时, mouseovermouseenter 事件都触发了,但是当移入各自的子元素li的时候,触发了左边ul上的mouseover事件,然而右边ul的mouseenter事件没有被触发。

造成以上现象本质上是 mouseenter 事件不支持冒泡所致。

如何模拟mouseenter事件。

可见mouseover事件因其具有冒泡的性质,在子元素内移动的时候,频繁被触发,如果我们不希望如此,可以使用mouseenter事件代替之,但是早期只有ie浏览器支持该事件,虽然现在大多数高级浏览器都支持了mouseenter事件,但是难免会有些兼容问题,所以如果可以自己手动模拟,那就太好了。

关键因素: relatedTarget 要想手动模拟mouseenter事件,需要对mouseover事件触发时的事件对象event属性relatedTarget了解。

relatedTarget事件属性返回与事件的目标节点相关的节点。 对于mouseover事件来说,该属性是鼠标指针移到目标节点上时所离开的那个节点。 对于mouseout事件来说,该属性是离开目标时,鼠标指针进入的节点。 对于其他类型的事件来说,这个属性没有用。

重新回顾一下文章最初的那张图,根据上面的解释,对于ul上添加的mouseover事件来说,relatedTarget只可能是

ul的父元素wrap(移入ul时,此时也是触发mouseenter事件的时候, 其实不一定,后面会说明 ), 或者ul元素本身(在其子元素上移出时), 又或者是子元素本身(直接从子元素A移动到子元素B)。

javascript中mouseenter与mouseover的异同
relatedTarget

根据上面的描述,我们可以对relatedTarget的值进行判断:如果值不是目标元素,也不是目标元素的子元素,就说明鼠标已移入目标元素而不是在元素内部移动。

条件1: 不是目标元素 很好判断 e.relatedTarget !== target(目标元素)

条件2:不是目标元素的子元素,这个应该怎么判断呢?

ele.contains

这里需要介绍一个新的api node.contains(otherNode) , 表示传入的节点是否为该节点的后代节点, 如果 otherNode 是 node 的后代节点或是 node 节点本身.则返回true , 否则返回 false

用法案例

<ul class="list">
 <li class="item">1</li>
 <li>2</li>
</ul>
<div class="test"></div>
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

那么利用contains这个api我们便可以很方便的验证条件2,接下来我们封装一个 contains(parent, node) 函数,专门用来判断 node 是不是 parent 的子节点

let contains = function (parent, node) {
 return parent !== node && parent.contains(node)
}

用我们封装过后的 contains 函数再去试试上面的例子

contains($list, $item) // true
contains($list, $test) // false
contains($list, $list) // false (主要区别在这里)

这个方法很方便地帮助我们解决了模拟mouseenter事件中的条件2,但是悲催的 ode.contains(otherNode) ,具有浏览器兼容性,在一些低级浏览器中是不支持的,为了做到兼容我们再来改写一下contains方法

let contains = docEle.contains ? function (parent, node) {
 return parent !== node && parent.contains(node)
} : function (parent, node) {
 let result = parent !== node

 if (!result) { // 排除parent与node传入相同的节点
 return result
 }

 if (result) {
 while (node && (node = node.parentNode)) {
  if (parent === node) {
  return true
  }
 }
 }

 return false
}

说了这么多,我们来看看用 mouseover 事件模拟 mouseenter 的最终代码

// callback表示如果执行mouseenter事件时传入的回调函数

let emulateEnterOrLeave = function (callback) {
 return function (e) {
 let relatedTarget = e.relatedTarget
 if (relatedTarget !== this && !contains(this, relatedTarget)) {
  callback.apply(this, arguments)
 }
 }
}

详细代码点击

代码示例点击

好了,我们已经通过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事件时传入的回调函数

let emulateEnterOrLeave = function (callback) {
 return function (e) {
 let relatedTarget = e.relatedTarget
 if (relatedTarget !== this && !contains(this, relatedTarget)) {
  callback.apply(this, arguments)
 }
 }
}

结尾

文中也许有些观点不够严谨,欢迎大家拍砖。

Javascript 相关文章推荐
jQuery实现的图文高亮滚动切换特效实例
Aug 10 Javascript
Jquery实现简单的轮播效果(代码管用)
Mar 14 Javascript
基于jQuery实现的查看全文功能【实用】
Dec 11 Javascript
js实现类bootstrap模态框动画
Feb 07 Javascript
Angular 4.x+Ionic3踩坑之Ionic 3.x界面传值详解
Mar 13 Javascript
javascript使用正则实现去掉字符串前面的所有0
Jul 23 Javascript
apicloud拉起小程序并传递参数的方法示例
Nov 21 Javascript
node静态服务器实现静态读取文件或文件夹
Dec 03 Javascript
js 获取本周、上周、本月、上月、本季度、上季度的开始结束日期
Feb 01 Javascript
使用纯前端JavaScript实现Excel导入导出方法过程详解
Aug 07 Javascript
用vite搭建vue3应用的实现方法
Feb 22 Vue.js
Ajax实现页面无刷新留言效果
Mar 24 Javascript
jquery仿京东商品放大浏览页面
Jun 06 #jQuery
Node.js连接mongodb实例代码
Jun 06 #Javascript
jquery实现图片放大点击切换
Jun 06 #jQuery
AngularJS 验证码60秒倒计时功能的实现
Jun 05 #Javascript
AngularJS 支付倒计时功能实现思路
Jun 05 #Javascript
解决bootstrap中使用modal加载kindeditor时弹出层文本框不能输入的问题
Jun 05 #Javascript
Vue2.0实现购物车功能
Jun 05 #Javascript
You might like
粗略计算在线时间,bug:ip相同
2006/12/09 PHP
去掉destoon资讯内容页keywords关键字自带的文章标题的方法
2014/08/21 PHP
php如何比较两个浮点数是否相等详解
2019/02/12 PHP
Javascript 布尔型分析
2008/12/22 Javascript
实现JavaScript中继承的三种方式
2009/10/16 Javascript
浅谈Javascript面向对象编程
2011/11/15 Javascript
jquery插件splitScren实现页面分屏切换模板特效
2015/06/16 Javascript
JS实现仿UC浏览器前进后退效果的实例代码
2017/07/17 Javascript
javascript使用链接跨域下载图片
2019/11/01 Javascript
JS对象属性的检测与获取操作实例分析
2020/03/17 Javascript
vue 内联样式style中的background用法说明
2020/08/05 Javascript
JavaScript事件委托实现原理及优点进行
2020/08/29 Javascript
vue将文件/图片批量打包下载zip的教程
2020/10/21 Javascript
Python Mysql数据库操作 Perl操作Mysql数据库
2009/01/12 Python
Python IDE PyCharm的基本快捷键和配置简介
2015/11/04 Python
Python HTTP客户端自定义Cookie实现实例
2017/04/28 Python
python使用Tkinter实现在线音乐播放器
2018/01/30 Python
浅析Python 抽象工厂模式的优缺点
2020/07/13 Python
通过代码实例了解Python sys模块
2020/09/14 Python
MAC彩妆英国官网:M·A·C UK
2018/05/30 全球购物
印度尼西亚值得信赖的第一家网店:Bhinneka
2018/07/16 全球购物
有趣的睡衣和礼物:LazyOne
2019/11/27 全球购物
全球性的众包图形设计市场:DesignCrowd
2021/02/02 全球购物
short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
2014/09/26 面试题
单位办理社保介绍信
2014/01/10 职场文书
新年团拜会主持词
2014/04/02 职场文书
科长竞争上岗演讲稿
2014/05/12 职场文书
计算机网络及管理学专业求职信
2014/06/05 职场文书
党委班子纠正“四风”问题整改措施
2014/10/28 职场文书
2014年教师思想工作总结
2014/12/03 职场文书
因工资原因离职的辞职信范文
2015/05/12 职场文书
公务员学习中国梦心得体会
2016/01/05 职场文书
2019年中,最受大众欢迎的6本新书
2019/08/07 职场文书
PHP命令行与定时任务
2021/04/01 PHP
Django如何与Ajax交互
2021/04/29 Python
Nginx location 和 proxy_pass路径配置问题小结
2021/09/04 Servers