JS23 mouseover与mouseenter

mouseover/mouseoutmouseenter/mouseleave的区别以及模拟。

区别

mouseovermouseout成对使用,事件会冒泡,当鼠标指针穿过被选元素子元素时,也会触发事件。

mouseentermouseleave成对使用,事件不会冒泡,只有在鼠标指针穿过被选元素时,才会触发mouseleave事件。

也就是说,假设把mouseover/mouseout事件绑定到了父元素,那么它其中的任何子元素只要发生了mouseover/mouseout事件,同时也会触发父元素的mouseover/mouseout事件,父元素的父元素也会触发……然后一直向上,就像池塘的气泡一样一直往上冒。

mouseenter/mouseleave不会发生事件冒泡,属于传统思维上的鼠标进出。

mouseover模拟mouseenter

1
2
3
4
<div id="container">
<div id="child1"></div>
<div id="child2"></div>
</div>

mouseover因为具有冒泡的性质,在子元素内移动的时候父元素的mouseover会被频繁触发。为了避免这个状况,可以使用mouseenter代替。

也可以使用mouseover结合relatedTarget模拟mouseenter的效果,对于mouseover事件来说,relatedTarget是鼠标移动目标节点时所离开的那个节点,对于mouseout事件时,改属性是离开目标时鼠标指针进入的节点

对于上面containermouseover事件,它的relatedTarget值可能是:

  1. container的父元素(即body),对应从外界移入container
  2. conainer元素本身,对应从其子元素上移入container
  3. 子元素本身,对应从child1移入child2

要模拟mouseenterrelatedTarget值只能是第一种情况,但是我们不能这样直接判断,这是因为有可能container的父元素不一定在各个方向包裹container,也就是说我们要判断的父元素可能是祖先元素的某一个,也就无法准确判断relatedTarget是哪个元素,所以需要通过排除2、3是更好的选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const container = document.querySelector('#container'),
child1 = document.querySelector('#child1'),
child2 = document.querySelector('#child2'),

function simulateEnter(parent, e) {
return (e.relatedTarget !== e.target) && (!parent.contains(e.relatedTarget))

}

container.addEventListener('mouseover', e => {
if (simulateEnter(container, e)) {
console.log(123)
}
});

判断父子节点包含关系使用了ele.contains这个API,用来表示传入的节点是否为改节点的后代节点。

mouseout模拟mouseleave原理是一样的。

可以直接封装为一个函数,注意this就是container,也就是我们绑定事件的对象,可以使用e.currentTarget来代替

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function simulateEnterOrLeave(cb) {
return function (e) {
const { relatedTarget } = e;
if(relatedTarget !== this && !this.contains(relatedTarget)) {
cb.apply(this, arguments);
}
}
}

function log() {
console.log(123);
}

container.addEventListener('mouseout', simulateEnterOrLeave(log));

参考