我到处寻找一个好的解决方案,但是我找不到任何一个不使用jQuery的方案。
是否有一种跨浏览器的常规方法(无需奇怪的技巧或容易破坏代码),可以检测元素外部的单击事件(可能具有子元素)?
我到处寻找一个好的解决方案,但是我找不到任何一个不使用jQuery的方案。
是否有一种跨浏览器的常规方法(无需奇怪的技巧或容易破坏代码),可以检测元素外部的单击事件(可能具有子元素)?
在document
上添加事件监听器,并使用Node.contains()
方法来查找事件的目标(即最内层被点击的元素)是否在指定的元素内。它甚至可以在IE5中工作。
const specifiedElement = document.getElementById('a')
// I'm using "click" but it works with any event
document.addEventListener('click', event => {
const isClickInside = specifiedElement.contains(event.target)
if (!isClickInside) {
// The click was OUTSIDE the specifiedElement, do something
}
})
var specifiedElement = document.getElementById('a');
//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
var isClickInside = specifiedElement.contains(event.target);
if (isClickInside) {
alert('You clicked inside A')
} else {
alert('You clicked outside A')
}
});
div {
margin: auto;
padding: 1em;
max-width: 6em;
background: rgba(0, 0, 0, .2);
text-align: center;
}
Is the click inside A or outside?
<div id="a">A
<div id="b">B
<div id="c">C</div>
</div>
</div>
你需要在文档级别处理点击事件。在事件对象中,你有一个target
属性,它是被点击的最内层DOM元素。通过这个属性,你可以检查它本身并向上遍历其父元素,直到文档元素,以确定其中是否包含你所观察的元素。
请参见jsFiddle上的示例。
document.addEventListener("click", function (e) {
var level = 0;
for (var element = e.target; element; element = element.parentNode) {
if (element.id === 'x') {
document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
return;
}
level++;
}
document.getElementById("out").innerHTML = "not x clicked";
});
像以往一样,由于addEventListener/attachEvent的原因,这不是跨浏览器兼容的,但它的工作方式如下。
当子元素被点击时,如果不是事件目标,则其某个父级元素是被监视的元素(我仅为此计算级别)。您还可以有一个布尔变量,用于判断是否找到该元素,以便从for循环内部不返回处理程序。我的示例仅限于处理程序仅在没有匹配项时才完成。
为了实现跨浏览器兼容性,我通常会这样做:
var addEvent = function (element, eventName, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventName, fn, useCapture);
}
else if (element.attachEvent) {
element.attachEvent(eventName, function (e) {
fn.apply(element, arguments);
}, useCapture);
}
};
这是一个跨浏览器兼容的代码,用于附加事件监听器/处理程序,包括在IE中重写this
为元素,就像jQuery为其事件处理程序所做的那样。有许多理由考虑要记住一些jQuery的要点;)
target
属性(正如 metadings 所说,它将是最内部的 DOM 元素)以及其所有父元素。如果您到达了要监视的元素,则表示用户在其中单击;如果您到达文档元素,则表示他/她在外部点击。或者,您可以在文档元素和监视元素上添加两个点击侦听器,后者使用 event.stopPropagation()
/ cancelBubble
,以便文档元素上的侦听器仅在用户在监视元素外单击时触发。 - JimmiThfor
的方法。出于好奇,不使用严格比较将变量中的元素与事件目标进行比较是否有任何原因? - oxygendocument.onclick = function(event){
var hasParent = false;
for(var node = event.target; node != document.body; node = node.parentNode)
{
if(node.id == 'div1'){
hasParent = true;
break;
}
}
if(hasParent)
alert('inside');
else
alert('outside');
}
您可以使用 composedPath() 来检查点击事件是否发生在可能具有子元素的目标 div 内或外:
const targetDiv = document.querySelector('#targetDiv')
document.addEventListener('click', (e) => {
const isClickedInsideDiv = e.composedPath().includes(targetDiv)
if (isClickedInsideDiv) {
console.log('clicked inside of div')
} else {
console.log('clicked outside of div')
}
})
let popup = document.querySelector('.parent-element')
popup.addEventListener('click', (e) => {
if (!e.target.closest('.child-element')) {
// clicked outside
}
});
我对它进行了大量的研究,以找到更好的方法。JavaScript 的 .contains 方法会在 DOM 中递归检查是否包含目标。我在一个 React 项目中使用了它,但当 React DOM 在设置状态时发生变化时,.contains 方法不起作用。因此,我想出了这个解决方案。
//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="mydiv">
<h2>
click outside this div to test
</h2>
Check click outside
</div>
</body>
</html>
//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'
let isCursorInside = false
//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
isCursorInside = true
console.log('cursor inside')
})
/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
isCursorInside = false
console.log('cursor outside')
})
document.addEventListener('click', function() {
//And if isCursorInside = false it means cursor is outside
if(!isCursorInside) {
alert('Outside div click detected')
}
})
var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element');
function clickedOrNot(e) {
if (e.target !== element) {
// action in the case of click outside
bodyTag[0].removeEventListener('click', clickedOrNot, true);
}
}
bodyTag[0].addEventListener('click', clickedOrNot, true);
另一个非常简单和快速的方法是将path
数组映射到监听器返回的事件对象中。如果您元素的id
或class
名称与数组中的某个名称匹配,则单击在您的元素内。
(如果您不想直接获取元素(例如:document.getElementById('...')
),则此解决方案可能很有用,例如在reactjs / nextjs应用程序中,在ssr中..)。
这里是一个例子:
document.addEventListener('click', e => {
let clickedOutside = true;
e.path.forEach(item => {
if (!clickedOutside)
return;
if (item.className === 'your-element-class')
clickedOutside = false;
});
if (clickedOutside)
// Make an action if it's clicked outside..
});
我希望这个答案能对你有所帮助!(如果我的解决方案不好或者你有什么改进意见,请告诉我。)