mouseenter与mouseover为什么这般牵扯不清?

媒介

原文地点

项目地点

不知道人人在口试或许事变过程当中有无被mouseovermouseenter(对应的是mouseoutmouseleave)事宜所搅扰。自身之前在口试的时刻就有被问到诸如mouseover和mouseenter事宜的异同之类的题目?当时没有答出来,一向也对这两个事宜有点模糊不清,趁着近来正在读zepto源码,预备写一篇这方面的文章,假如有毛病,请人人斧正。

《mouseenter与mouseover为什么这般牵扯不清?》

<!–more–>

mouseenter与mouseover的异同?

要说清晰mouseenter与mouseover有什么差别,或许能够从两方面去讲。

  1. 是不是支撑冒泡

  2. 事宜的触发机遇

先来看一张图,对这两个事宜有一个简朴直观的感觉。

《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事宜

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

《mouseenter与mouseover为什么这般牵扯不清?》

我们给摆布双方的ul离别增加了mouseovermouseenter事宜,当鼠标进入摆布双方的ul时,mouseovermouseenter事宜都触发了,然则当移入各自的子元素li的时刻,触发了左侧ul上的mouseover事宜,但是右侧ul的mouseenter事宜没有被触发。

形成以上征象本质上是mouseenter事宜不支撑冒泡而至。

怎样模仿mouseenter事宜。

可见mouseover事宜因其具有冒泡的性子,在子元素内挪动的时刻,频仍被触发,假如我们不愿望云云,能够运用mouseenter事宜替代之,然则初期只要ie浏览器支撑该事宜,虽然如今大多数高等浏览器都支撑了mouseenter事宜,然则难免会有些兼容题目,所以假如能够自身手动模仿,那就太好了。

关键因素: relatedTarget 要想手动模仿mouseenter事宜,须要对mouseover事宜触发时的事宜对象event属性relatedTarget相识。

  1. relatedTarget事宜属性返回与事宜的目的节点相干的节点。

  2. 关于mouseover事宜来讲,该属性是鼠标指针移到目的节点上时所脱离的谁人节点。

  3. 关于mouseout事宜来讲,该属性是脱离目的时,鼠标指针进入的节点。

  4. 关于其他范例的事宜来讲,这个属性没有用。

从新回忆一下文章最初的那张图,依据上面的诠释,关于ul上增加的mouseover事宜来讲,relatedTarget只多是

  1. ul的父元素wrap(移入ul时,此时也是触发mouseenter事宜的时刻, 实在不肯定,背面会申明),

  2. 或许ul元素自身(在其子元素上移出时),

  3. 又或许是子元素自身(直接从子元素A挪动到子元素B)。

《mouseenter与mouseover为什么这般牵扯不清?》

依据上面的形貌,我们能够对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)  

结果预览

《mouseenter与mouseover为什么这般牵扯不清?》

细致代码点击

代码示例点击

好了,我们已经由过程mouseove事宜完全的模仿了mouseenter事宜,然则反过甚来看看

关于ul上增加的mouseover事宜来讲,relatedTarget只多是

  1. ul的父元素wrap(移入ul时,此时也是触发mouseenter事宜的时刻, 实在不肯定,背面会申明),

  2. 或许ul元素自身(在其子元素上移出时),

  3. 又或许是子元素自身(直接从子元素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)
    }
  }
}

细致代码点击

代码示例点击

末端

文中或许有些看法不够严谨,迎接人人拍砖。

原文地点

项目地点

    原文作者:谦龙
    原文地址: https://segmentfault.com/a/1190000009667405
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞