媒介
不知道人人在口试或许事变过程当中有无被
mouseover
和mouseenter
(对应的是mouseout
和mouseleave
)事宜所搅扰。自身之前在口试的时刻就有被问到诸如mouseover和mouseenter事宜的异同之类的题目?当时没有答出来,一向也对这两个事宜有点模糊不清,趁着近来正在读zepto源码,预备写一篇这方面的文章,假如有毛病,请人人斧正。
<!–more–>
mouseenter与mouseover的异同?
要说清晰mouseenter与mouseover有什么差别,或许能够从两方面去讲。
是不是支撑冒泡
事宜的触发机遇
先来看一张图,对这两个事宜有一个简朴直观的感觉。
再看看官网对mouseenter的诠释
mouseenter | onmouseenter event.aspx)
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.
也许意义是说:当鼠标从元素的边境以外移入元素的边境以内时,事宜被触发。而当鼠标自身在元素边境内时,要触发该事宜,必需先将鼠标移出元素边境外,再次移入才触发。(英语比较渣?,拼集看哈)
Unlike the onmouseover event, the onmouseenter event does not bubble.
也许意义是:和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事宜触发时的事宜对象event属性relatedTarget相识。
relatedTarget事宜属性返回与事宜的目的节点相干的节点。
关于mouseover事宜来讲,该属性是鼠标指针移到目的节点上时所脱离的谁人节点。
关于mouseout事宜来讲,该属性是脱离目的时,鼠标指针进入的节点。
关于其他范例的事宜来讲,这个属性没有用。
从新回忆一下文章最初的那张图,依据上面的诠释,关于ul上增加的mouseover事宜来讲,relatedTarget只多是
ul的父元素wrap(移入ul时,此时也是触发mouseenter事宜的时刻, 实在不肯定,背面会申明),
或许ul元素自身(在其子元素上移出时),
又或许是子元素自身(直接从子元素A挪动到子元素B)。
依据上面的形貌,我们能够对relatedTarget的值举行推断:假如值不是目的元素,也不是目的元素的子元素,就申明鼠标已移入目的元素而不是在元素内部挪动。
前提1: 不是目的元素很好推断e.relatedTarget !== target(目的元素)
前提2:不是目的元素的子元素,这个应当怎样推断呢?
ele.contains
这里须要引见一个新的api [node.contains(otherNode)
](https://developer.mozilla.org… 示意传入的节点是不是为该节点的子女节点, 假如 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)
}
}
}
模仿mouseenter与原生mouseenter事宜结果对照
html
<div class="wrap">
wrap, mouseenter
<ul class="mouseenter list">
count: <span class="count"></span>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
<div class="wrap">
wrap, emulate mouseenter,用mouseover模仿完成mouseenter
<ul class="emulate-mouseenter list">
count: <span class="count"></span>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
css
.wrap{
width: 50%;
box-sizing: border-box;
float: left;
}
.wrap, .list{
border: solid 1px green;
padding: 30px;
margin: 30px 0;
}
.list{
border: solid 1px red;
}
.list li{
border: solid 1px blue;
padding: 10px;
margin: 10px;
}
.count{
color: red;
}
javascript
let $mouseenter = document.querySelector('.mouseenter')
let $emulateMouseenter = document.querySelector('.emulate-mouseenter')
let $enterCount = document.querySelector('.mouseenter .count')
let $emulateMouseenterCounter = document.querySelector('.emulate-mouseenter .count')
let addCount = function (ele, start) {
return function () {
ele.innerHTML = ++start
}
}
let docEle = document.documentElement
let contains = docEle.contains ? function (parent, node) {
return parent !== node && parent.contains(node)
} : function (parent, node) {
let result = parent !== node
if (!result) {
return result
}
if (result) {
while (node && (node = node.parentNode)) {
if (parent === node) {
return true
}
}
}
return false
}
let emulateMouseenterCallback = addCount($emulateMouseenterCounter, 0)
let emulateEnterOrLeave = function (callback) {
return function (e) {
let relatedTarget = e.relatedTarget
if (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事宜时传入的回调函数
let emulateEnterOrLeave = function (callback) {
return function (e) {
let relatedTarget = e.relatedTarget
if (relatedTarget !== this && !contains(this, relatedTarget)) {
callback.apply(this, arguments)
}
}
}
末端
文中或许有些看法不够严谨,迎接人人拍砖。