检测外部点击元素(香草JavaScript)


84

我到处都在寻找一个好的解决方案,但是找不到不使用jQuery的解决方案。

是否有一种跨浏览器的常规方法(没有怪异的hack或容易破解的代码)来检测元素外部的单击(可能没有子元素)?


会在body标签上设置onclick事件处理程序吗?
Benjamin Toueg

Answers:


192

向其中添加事件侦听器,document并用于Node.contains()查找事件的目标(最里面被单击的元素)是否在您指定的元素内。即使在IE5中也可以使用

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) {
    //the click was outside the specifiedElement, do something
  }
});


14
似乎是我最优雅的解决方案。
玛丽安

3
这是我见过的最好的解决方案。
HelloWorld

25

您需要在文档级别处理click事件。在事件对象中,您具有一个target属性,即被单击的最里面的DOM元素。使用此方法,您可以检查自己并向上移动其父级,直到document元素(如果其中一个是您监视的元素)。

请参阅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,它与跨浏览器的浏览器不兼容,但它的工作原理是这样的。

如果不是event.target,则单击一个孩子,但单击它的父母之一是watched元素(我只是在为此计算级别)。如果没有找到该元素,也可能有一个布尔var,以不从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);
  }
};

这是跨浏览器兼容的代码,用于将事件侦听器/处理程序(包括thisIE中的包含重写)附加为元素,就像jQuery对其事件处理程序所做的那样。有很多论点要考虑一些jQuery;)


4
如果用户单击该元素的子元素怎么办?
Tiberiu-Ionuț Stan,2013年

3
使用此解决方案,您可以检查target属性(如元数据所说,将是最里面的DOM元素)-及其所有父元素。您到达监视元素(这意味着用户在其中单击),或者到达文档元素(在这种情况下,他/她在外部单击)。或者,您可以添加两个单击侦听器-一个在document元素上,另一个在监视的元素上,使用event.stopPropagation()/,cancelBubble以便仅通过在watched元素外部的单击触发文档元素上的侦听器。
JimmiTh

2
这是对的一些巧妙用法for。出于好奇,是否有任何理由不将变量中的元素与事件中的目标进行比较(使用严格比较)?
Tiberiu-Ionuț Stan 2013年

@ Tiberiu-IonuțStan是的,如果您使用引用而不是ID“ x”来创建此“ watch”,则建议您使用具有三等号的element === watchedElement。
metadings

您的解决方案效果很好。这可能是个人问题,但我宁愿分享。我有同样的需要,但是单击的按钮显示一个div,当用户在外部单击时,该div应该隐藏。该解决方案的问题在于,不再显示div。我在这里为示例分叉了您的代码:jsfiddle.net/bx5hnL2h
Alexandre Schmidt

7

这个怎么样:

jsBin演示

document.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');
} 

0

要通过在元素外部单击来隐藏元素,我通常应用以下简单代码:

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);


0

解决此问题的另一种非常简单快捷的方法是将数组映射path到侦听器返回的事件对象中。如果元素的idclass名称与数组中的元素之一匹配,则单击位于元素内部。

(如果您不想直接获取元素(例如document.getElementById('...'),例如,在reacts / 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..
    });

希望这个答案对您有所帮助!(让我知道我的解决方案不是一个好的解决方案,或者您是否看到有待改进的地方。)


嵌套元素呢?
Tiberiu-Ionuț Stan,

@ Tiberiu-IonuțStan您可以访问从父级到子级的整个节点列表,因此您可以轻松地操作它们。
hpapier19年

突然,您的解决方案不再简单了。
Tiberiu-Ionuț Stan,

@ Tiberiu-IonuțStan为什么?我个人认为此解决方案在React或复杂的JS环境中是最简单的。如果dom需要时间来加载,则无需任何引用,也无需实施解决方案,您就可以控制更多。
hpapier19年

0

我对此进行了大量研究,以找到更好的方法。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')
  }
})

工作演示jsfiddle

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.