在没有jQuery的情况下,防止悬停父div的子元素时onmouseout

我在绝对定位div中的onmouseout函数遇到了问题。当鼠标击中div中的子元素时,mouseout事件会触发,但我不希望它在鼠标离开父元素,绝对div之前触发。

我如何防止mouseout事件在没有jquery的情况下击中子元素时触发。

我知道这与事件冒泡有关,但我没有找到如何解决这个问题的方法。

我在这里找到了一个类似的帖子:如何禁用由子元素触发的鼠标退出事件?

但是,该解决方案使用jQuery。

174324 次浏览

我认为Quirksmode有你需要的所有答案(不同的浏览器冒泡行为和mouseenter / mouseleave事件),但我认为最常见的结论,事件冒泡混乱使用框架,如JQuery或Mootools(有mouseentermouseleave事件,这正是你直觉会发生的)。

看看他们是怎么做的,如果你想的话,自己做吧 或者你可以创建自定义“精益平均”版本的Mootools与事件部分(及其依赖)。

有两种方法可以处理这个问题。

1)检查事件。目标结果在你的回调,看看它是否匹配你的父div

var g_ParentDiv;


function OnMouseOut(event) {
if (event.target != g_ParentDiv) {
return;
}
// handle mouse event here!
};




window.onload = function() {
g_ParentDiv = document.getElementById("parentdiv");
g_ParentDiv.onmouseout = OnMouseOut;
};


<div id="parentdiv">
<img src="childimage.jpg" id="childimg" />
</div>

2)或者使用事件捕获和调用事件。回调函数中的stopPropagation

var g_ParentDiv;


function OnMouseOut(event) {


event.stopPropagation(); // don't let the event recurse into children


// handle mouse event here!
};




window.onload = function() {
g_ParentDiv = document.getElementById("parentdiv");
g_ParentDiv.addEventListener("mouseout", OnMouseOut, true); // pass true to enable event capturing so parent gets event callback before children
};


<div id="parentdiv">
<img src="childimage.jpg" id="childimg" />
</div>
function onMouseOut(event) {
//this is the original element the event handler was assigned to
var e = event.toElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
return;
}
alert('MouseOut');
// handle mouse event here!
}






document.getElementById('parent').addEventListener('mouseout',onMouseOut,true);

我做了一个快速的JsFiddle演示,所有的CSS和HTML需要,检查它…

用于跨浏览器支持的编辑 FIXED链接

请注意,这只检查直接父元素,如果父div嵌套了子元素,那么你必须以某种方式遍历元素的父元素,寻找“原始元素”

编辑的例子用于嵌套子

编辑修复希望跨浏览器

function makeMouseOutFn(elem){
var list = traverseChildren(elem);
return function onMouseOut(event) {
var e = event.toElement || event.relatedTarget;
if (!!~list.indexOf(e)) {
return;
}
alert('MouseOut');
// handle mouse event here!
};
}


//using closure to cache all child elements
var parent = document.getElementById("parent");
parent.addEventListener('mouseout',makeMouseOutFn(parent),true);


//quick and dirty DFS children traversal,
function traverseChildren(elem){
var children = [];
var q = [];
q.push(elem);
while (q.length > 0) {
var elem = q.pop();
children.push(elem);
pushAll(elem.children);
}
function pushAll(elemArray){
for(var i=0; i < elemArray.length; i++) {
q.push(elemArray[i]);
}
}
return children;
}

和一个新的< >强JSFiddle < / >强编辑更新链接

我检查原始元素的偏移量以获得元素边界的页面坐标,然后确保只有当鼠标退出超出这些边界时才触发鼠标退出操作。很脏,但很管用。

$(el).live('mouseout', function(event){
while(checkPosition(this, event)){
console.log("mouseovering including children")
}
console.log("moused out of the whole")
})


var checkPosition = function(el, event){
var position = $(el).offset()
var height = $(el).height()
var width = $(el).width()
if (event.pageY > position.top
|| event.pageY < (position.top + height)
|| event.pageX > position.left
|| event.pageX < (position.left + width)){
return true
}
}

我用这个让它像魔法一样起作用:

function HideLayer(theEvent){
var MyDiv=document.getElementById('MyDiv');
if(MyDiv==(!theEvent?window.event:theEvent.target)){
MyDiv.style.display='none';
}
}

啊,MyDiv标签是这样的:

<div id="MyDiv" onmouseout="JavaScript: HideLayer(event);">
<!-- Here whatever divs, inputs, links, images, anything you want... -->
<div>

通过这种方式,当onmouseout转到子节点、孙子节点等时……style.display='none'没有被执行;但是当onmouseout退出MyDiv时,它会运行。

所以不需要停止传播,使用计时器等等……

谢谢例子,我可以从他们做这个代码。

希望这能帮助到一些人。

也可以这样改进:

function HideLayer(theLayer,theEvent){
if(theLayer==(!theEvent?window.event:theEvent.target)){
theLayer.style.display='none';
}
}

然后DIVs标签是这样的:

<div onmouseout="JavaScript: HideLayer(this,event);">
<!-- Here whatever divs, inputs, links, images, anything you want... -->
<div>

所以更一般,不只是一个div,不需要在每一层上添加id="..."

感谢阿姆贾德·马萨德,他激励了我。

我有以下解决方案,似乎在IE9, FF和Chrome和代码很短(没有复杂的闭包和横向子东西):

    DIV.onmouseout=function(e){
// check and loop relatedTarget.parentNode
// ignore event triggered mouse overing any child element or leaving itself
var obj=e.relatedTarget;
while(obj!=null){
if(obj==this){
return;
}
obj=obj.parentNode;
}
// now perform the actual action you want to do only when mouse is leaving the DIV
}
var elem = $('#some-id');
elem.mouseover(function () {
// Some code here
}).mouseout(function (event) {
var e = event.toElement || event.relatedTarget;
if (elem.has(e).length > 0) return;


// Some code here
});
下面是一个基于下面内容的更优雅的解决方案。 它解释了从一个以上层次的孩子中冒出来的事件。

function onMouseOut(this, event) {
//this is the original element the event handler was assigned to
var e = event.toElement || event.relatedTarget;


//check for all children levels (checking from bottom up)
while(e && e.parentNode && e.parentNode != window) {
if (e.parentNode == this||  e == this) {
if(e.preventDefault) e.preventDefault();
return false;
}
e = e.parentNode;
}


//Do something u need here
}


document.getElementById('parent').addEventListener('mouseout',onMouseOut,true);

我找到了一个很简单的解决办法,

只需使用onmouseleave="myfunc()"事件,而不是onmousout="myfunc()"事件

在我的代码中,它工作了!!

例子:

<html>
<head>
<script type="text/javascript">
function myFunc(){
document.getElementById('hide_div').style.display = 'none';
}
function ShowFunc(){
document.getElementById('hide_div').style.display = 'block';
}
</script>
</head>
<body>
<div onmouseleave="myFunc()" style='border:double;width:50%;height:50%;position:absolute;top:25%;left:25%;'>
Hover mouse here
<div id='child_div' style='border:solid;width:25%;height:25%;position:absolute;top:10%;left:10%;'>
CHILD <br/> It doesn't fires if you hover mouse over this child_div
</div>
</div>
<div id="hide_div" >TEXT</div>
<a href='#' onclick="ShowFunc()">Show "TEXT"</a>
</body>
</html>

mouseout函数的示例:

<html>
<head>
<script type="text/javascript">
function myFunc(){
document.getElementById('hide_div').style.display = 'none';
}
function ShowFunc(){
document.getElementById('hide_div').style.display = 'block';
}
</script>
</head>
<body>
<div onmouseout="myFunc()" style='border:double;width:50%;height:50%;position:absolute;top:25%;left:25%;'>
Hover mouse here
<div id='child_div' style='border:solid;width:25%;height:25%;position:absolute;top:10%;left:10%;'>
CHILD <br/> It fires if you hover mouse over this child_div
</div>
</div>
<div id="hide_div">TEXT</div>
<a href='#' onclick="ShowFunc()">Show "TEXT"</a>
</body>
</html>

希望能有所帮助。

尝试mouseleave()

例子:

<div id="parent" mouseleave="function">
<div id="child">


</div>
</div>

;)

如果你添加(或拥有)一个CSS类或id到父元素,那么你可以这样做:

<div id="parent">
<div>
</div>
</div>


JavaScript:
document.getElementById("parent").onmouseout = function(e) {
e = e ? e : window.event //For IE
if(e.target.id == "parent") {
//Do your stuff
}
}

所以只有当事件在父div上时,才会执行东西。

一个更简单的纯CSS解决方案在大多数情况下都有效,可以通过将子代的pointer-events设置为none来删除它们

.parent * {
pointer-events: none;
}

浏览器支持:IE11 +

如果你正在使用jQuery,你还可以使用“mouseleave”函数,它可以为你处理所有这些问题。

$('#thetargetdiv').mouseenter(do_something);
$('#thetargetdiv').mouseleave(do_something_else);

Do_something将在鼠标进入targetdiv或其任何子div时触发,do_something_else仅在鼠标离开targetdiv及其任何子div时触发。

使用onmouseleave

或者,在jQuery中使用mouseleave()

这正是你要找的东西。例子:

<div class="outer" onmouseleave="yourFunction()">
<div class="inner">
</div>
</div>

或者,在jQuery中:

$(".outer").mouseleave(function(){
//your code here
});

一个例子是在这里

我只是想和你分享一些东西 我在ng-mouseenterng-mouseleave事件中遇到了一些困难。< / p >

案例分析:

我创建了一个浮动导航菜单,当光标在图标上时切换

.

.

.
  • 要处理菜单上的显示/隐藏,我切换一个类 李ng-class="{down: vm.isHover}" < / >
  • 要切换vm.isHover,我使用ng鼠标事件 ng-mouseenter="vm.isHover = true" < br > 李ng-mouseleave="vm.isHover = false" < / >

现在,一切都很好,像预期的那样工作 解决方法简单明了。< / p >

接下来的问题是:

在一个特定的视图中,我有一个元素列表 当光标在列表元素上时,我添加了一个操作面板 我使用与上面相同的代码来处理该行为。< / p >

存在的问题:

我发现当我的光标在浮动导航菜单上时,也在一个元素的顶部,彼此之间有冲突 动作面板显示,浮动导航被隐藏。< / p > 问题是即使光标在浮动导航菜单上,列表元素ng-mouseenter也会被触发 这对我来说没有意义,因为我希望自动中断鼠标传播事件 我必须说我很失望,我花了一些时间才找出那个问题

第一个想法:

我试着用这些:

  • $event.stopPropagation()
  • $event.stopImmediatePropagation()

我结合了很多ng指针事件(mouemove, mouveover,…),但没有一个帮助我。

CSS解决方案:

我用一个简单的css属性找到了解决方案,我越来越多地使用它:

pointer-events: none;

基本上,我这样使用它(在我的列表元素上):

ng-style="{'pointer-events': vm.isHover ? 'none' : ''}"

有了这个棘手的,ng-mouse事件将不再被触发,我的浮动导航菜单将不再关闭自己,当光标在它和列表中的一个元素上时。

更进一步说:

如你所料,这个解决办法可行,但我不喜欢它 我们不能控制我们的事件,这很糟糕 另外,你必须访问vm.isHover作用域来实现这一点,这可能是不可能的,也可能是可能的,但以某种方式是脏的 如果有人想看的话,我可以做一个小提琴

然而,我没有其他的解决方案 说来话长,我不能给你土豆,所以请原谅我,我的朋友 不管怎样,pointer-events: none是生命,所以记住它

如果你可以访问mouseout方法中事件所附加的元素,你可以使用contains()来查看event.relatedTarget是否是子元素。

由于event.relatedTarget是鼠标传递到的元素,如果它不是子元素,则鼠标已经移出了元素。

div.onmouseout = function (event) {
if (!div.contains(event.relatedTarget)) {
// moused out of div
}
}
虽然你提到的解决方案使用jquery, mouseentermouseleave是原生dom事件,所以你可以不使用jquery

在Angular 5、6和7上

<div (mouseout)="onMouseOut($event)"
(mouseenter)="onMouseEnter($event)"></div>

然后在

import {Component,Renderer2} from '@angular/core';
...
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit, OnDestroy {
...
public targetElement: HTMLElement;


constructor(private _renderer: Renderer2) {
}


ngOnInit(): void {
}


ngOnDestroy(): void {
//Maybe reset the targetElement
}


public onMouseEnter(event): void {
this.targetElement = event.target || event.srcElement;
console.log('Mouse Enter', this.targetElement);
}


public onMouseOut(event): void {
const elementRelated = event.toElement || event.relatedTarget;
if (this.targetElement.contains(elementRelated)) {
return;
}
console.log('Mouse Out');
}
}

用onmouseleave代替onmouseout。

你还没有向我们展示你的具体代码,所以我不能在你的具体例子中告诉你如何做到这一点。

但是它很简单:用onmouseleave代替onmouseout。

就是这样:)所以,很简单:)

如果不知道怎么做,请参阅解释:

https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_onmousemove_leave_out

和平的蛋糕:)尽情享受吧。

简单地,我们可以检查e.relatedTarget是否有子类,如果为真则返回函数。

    if ($(e.relatedTarget).hasClass("ctrl-btn")){
return;
}

这是为我工作的代码,我用于html5视频播放,暂停按钮切换悬停视频元素

element.on("mouseover mouseout", function(e) {


if(e.type === "mouseout"){


if ($(e.relatedTarget).hasClass("child-class")){
return;
}


}


});

有一个简单的方法可以让它起作用。元素和所有子元素都设置了相同的类名,那么:

element.onmouseover = function(event){
if (event.target.className == "name"){
/*code*/
}
}

香草也可以用这种方法

document.querySelector('.product_items') && document.querySelector('.product_items').addEventListener('mouseleave', () => updateCart())




const updateCart = () => {
let total = 0;
document.querySelectorAll('input') && document.querySelectorAll('input').forEach(item => total += +item.value)
document.getElementById('total').innerHTML = total
}
<div class="product_items">
<div class="product_item">
<div class="product_name">
</div>
<div class="multiply__btn">
<button type="button">-</button>
<input name="test" type="text">
<button type="button">+</button>
</div>
</div>
<div class="product_item">
<div class="product_name">
</div>
<div class="multiply__btn">
<button type="button">-</button>
<input name="test" type="text">
<button type="button">+</button>
</div>
</div>
<div class="product_item">
<div class="product_name">
</div>
<div class="multiply__btn">
<button type="button">-</button>
<input name="test" type="text">
<button type="button">+</button>
</div>
</div>
</div>


<div id="total"></div>

如果出于某种原因你不想使用mouseentermouseleave事件,你可以使用mouseover/mouseout加上一个小的" debouncquot;。

这个想法依赖于这样一个事实:当你的事件处理程序在各个子元素....之间跨越边界时,它会接收到out,然后是一个新的over除非鼠标真的离开(超过反弹周期)。这似乎比在每个事件上爬行dom节点更简单。

如果你&;在假设你有一个真正的out之前,你可以有效地忽略所有这些从子元素冒出来的out/over事件。

如果子元素也有over和/或out事件的监听器,则此方法无效。子元素的处理程序调用event.stopPropogation()来阻止事件向附加处理程序的父元素起泡。如果您控制代码,这并不一定是问题,但您应该注意到这一点。

示例代码

javascript




function mouseOverOutDebounce (element, debounceMs, mouseOverFn, mouseOutFn) {
var over = false,
debounceTimers = [];


function mouseOver (evt) {
if (over) {  // already OVER, existing interaction
while (debounceTimers.length > 0) { // then we had a pending mouseout(s), cancel
window.clearTimeout(debounceTimers.shift());
}
}
else { // new OVER
over = true;
mouseOverFn(evt);
}
}
function mouseOut (evt) {
if (!over) return;  // already OUT, ignore.


debounceTimers.push(window.setTimeout(function () {
over = false;
mouseOutFn(evt);
}, debounceMs));
}


function removeEventListeners () {
element.removeEventListener('mouseover', mouseOver);
element.removeEventListener('mouseout', mouseOut);
}
    

element.addEventListener('mouseover', mouseOver);
element.addEventListener('mouseout', mouseOut);


return removeEventListeners;
}


var someEl = document.querySelector('.container'),
textarea = document.querySelector('textarea'),
mouseOver = function (evt) { report('mouseOVER', evt); },
mouseOut = function (evt) { report('mouseOUT', evt); },
removeEventListeners = mouseOverOutDebounce(someEl, 200, mouseOver, mouseOut);


function report(msg, data) {
console.log(msg, data);
textarea.value = textarea.value + msg + '\n';
}

超文本标记语言

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
html, body {
margin: 0;
padding: 0;
}
body {
margin: 5%;
}
.container {
width: 300px;
height: 600px;
border: 10px solid red;
background-color:  #dedede;
float: left;
}
.container .square {
width: 100px;
height: 100px;
background-color: #2086cf;
margin: -10px 0 0 -10px;
}
textarea {
margin-left: 50px;
width: 800px;
height: 400px;
background-color: #464646;
font-family: monospace;
color: white;
}
.bar {
width: 2px;
height: 30px;
display: inline-block;
margin-left: 2px;
background-color: pink;
}
</style>
</head>
<body>


<div class="container">
<div class="square"></div>


<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>


<textarea></textarea>




<script src="interactions.js"></script>
</body>
</html>

小提琴

https://jsfiddle.net/matp/9bhjkLct/5/