检测元素外的点击(vanilla JavaScript)

IT技术 javascript
2021-02-16 23:09:47

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

是否有跨浏览器的正常方式(没有奇怪的黑客或容易破坏的代码)来检测元素外部的点击(可能有也可能没有孩子)?

6个回答

添加事件侦听器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
  }
});

对我来说似乎是最优雅的解决方案。
2021-04-24 23:09:47
这是我见过的最好的解决方案。
2021-04-27 23:09:47
我太爱你了
2021-05-08 23:09:47

您需要在文档级别处理单击事件。在事件对象中,您有一个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,这不是跨坏浏览器兼容的,但它的工作原理是这样的。

点击一个孩子,当不是 event.target 时,但它的父母之一是被监视的元素(我只是为此计算级别)。你也可能有一个布尔变量,如果元素被找到与否,则不从 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);
  }
};

这是跨浏览器兼容的代码,用于附加事件侦听器/处理程序,this在 IE 中包含重写,作为元素,就像 jQuery 为其事件处理程序所做的那样。有很多论据可以考虑一些 jQuery ;)

@Tiberiu-IonuțStan 是的,如果您使用引用而不是 id 'x' 创建此“手表”,我建议您使用 element === watchElement 使用三重等号。
2021-04-20 23:09:47
这是for. 出于好奇,是否有任何理由不将变量中的元素与事件中的目标进行比较(使用严格比较)?
2021-04-24 23:09:47
使用此解决方案,您可以检查target属性(正如 metadings 所说,它将是最内部的 DOM 元素) - 及其所有父元素。要么你到达了你的监视元素,这意味着用户在里面点击了它,要么你到达了文档元素,在这种情况下他/她点击了外面。或者,您可以添加两个单击侦听器 - 一个在文档元素上,另一个在被监视元素上,它使用event.stopPropagation()/cancelBubble以便文档元素上的侦听器仅由被监视元素外的单击触发。
2021-04-26 23:09:47
您的解决方案效果很好。这可能是个人问题,但我更愿意分享。我有同样的需求,但是单击按钮会显示一个 div,当用户单击外部时,该 div 应该被隐藏。此解决方案的问题是 div 不再显示。我在这里分叉了你的代码作为一个例子:jsfiddle.net/bx5hnL2h
2021-04-29 23:09:47
如果用户单击元素的子元素怎么办?
2021-05-15 23:09:47

这个怎么样:

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

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

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

(如果您不想直接获取元素,此解决方案会很有用(例如: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..
    });

我希望这个答案能帮到你!(如果我的解决方案不是一个好的解决方案,或者您认为需要改进的地方,请告诉我。)

突然间,您的解决方案不再简单了。
2021-04-15 23:09:47
@Tiberiu-IonuțStan 为什么?我个人认为这个解决方案在 React 或复杂的 JS 环境中是最简单的。不需要任何 ref,如果 dom 有时间加载,则不需要实施解决方案,您有更多的控制权。
2021-04-17 23:09:47
@Tiberiu-IonuțStan 您可以访问从父节点到子节点的整个节点列表,因此您可以轻松操作它们。
2021-05-02 23:09:47
嵌套元素呢?
2021-05-04 23:09:47