【问题标题】:Listening for clicks only inside a div without interfering with its children (a hitbox)?只在 div 内监听点击而不干扰其子级(hitbox)?
【发布时间】:2017-04-03 12:41:00
【问题描述】:

我有以下 DOM 结构:

#container {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: blue;
}

#content {
  position: absolute;
  width: 90%;
  height: 90%;
  background-color: yellow;
}

#content_child {
  position: absolute;
  width: 30px;
  height: 30px;
  left: 150px;
  top: 150px;
  background-color: red;
}
<div id="container">
  <div id="content">
    <div id="content_text">Hello</div>
    <div id="content_child"></div>
  </div>
</div>

#content_text#content_child 会有各种鼠标事件。我想检测#container 上的鼠标事件。但是,如果我只是将事件附加到#container,它们将被它的孩子激活,这是不希望的。我的要求如下(我以点击事件为例):

  • #container被点击时,我希望它的事件触发
  • 当一个孩子以图形方式位于 其 div(如 #content_text)被点击时,我希望孩子和 #container 的事件都被触发李>
  • 当一个孩子以图形方式位于外部其 div(如#content_child)被点击时,我希望触发孩子的事件

我事先知道哪些孩子会在 div 的外部和内部。

我能想到两种方法,都相当有缺陷:

  1. 将鼠标事件附加到#container,并为div 之外的每个子项添加与附加到#container 相同的事件,但使处理程序不做任何事情并使它们不传播。这有点笨拙。
  2. 添加一个“hit area”/“hitbox” div...某处。这将是一个透明 div,它是 #container 的子代(并且大小相同),它会注册点击,这不会传播给它的兄弟姐妹的孩子,即 #content 的孩子,因为它们是兄弟姐妹。如果我把它放在#content 之前,所有的hitbox 事件都会被#content 吃掉,而且它们永远不会触发。如果我把它放在后面,反过来也是同样的问题。我不能让两者同时在同一个地方收听相同的事件,而不将它们传递给他们所有的孩子。

有没有一种方法可以在不进行重大重组或花哨的 Javascript 的情况下实现 hitbox 类型的行为(我使用的是 Elm,所以两者都不是一个选项)?

【问题讨论】:

  • event.target 和 currentTarget 会帮助你。
  • 按照建议做。 event.currentTarget 是对附加事件的元素的引用(在您的情况下是容器),而 event.target 是对已单击的元素的引用。您可以使用例如 element.match 函数,将点击的元素与给定的 css 选择器进行匹配。
  • 事件目标不关心元素在哪里显示,但要考虑 DOM 结构。所以检查目标对“图形内部/外部”的东西没有帮助;在这种情况下,您必须计算点击是发生在元素“内部”还是“外部”。

标签: javascript html css events elm


【解决方案1】:

将处理程序附加到#container。当您收到事件时,如果它通过了一个 以图形方式在容器外的子节点,则忽略它。

您可以通过检查事件对象上的target 以及(如有必要)其parentNode 等来确定单击到达容器所采用的路径,直到到达容器为止。

这是一个例子(在这个例子中,我硬编码了我对孩子是否以图形方式在父级内部的检查,因为你说你已经知道了;不需要去弄清楚它举个简单的例子):

function hook(selector, event, handler) {
  var elements = document.querySelectorAll(selector);
  Array.prototype.forEach.call(elements, function(el) {
    el.addEventListener(event, handler, false);
  });
}
// container's click handler
hook("#container", "click", function(e) {
  if (passedThroughChildOutsideBox(e, this)) {
    // Ignore
  } else {
    console.log("container click");
  }
});
// child handlers
hook("#content_text, #content_child", "click", function(e) {
  console.log(this.id + " click");
});

// Fake detection
function passedThroughChildOutsideBox(e, container) {
  var node = e.target;
  while (node && node !== container) {
    if (node.id === "content_child") { // Again, just hardcoded for demo
      return true;
    }
    node = node.parentNode;
  }
  return false;
}
#container {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: blue;
}

#content {
  position: absolute;
  width: 90%;
  height: 90%;
  background-color: yellow;
}

#content_child {
  position: absolute;
  width: 30px;
  height: 30px;
  left: 150px;
  top: 150px;
  background-color: red;
}
<div id="container">
  <div id="content">
    <div id="content_text">Hello</div>
    <div id="content_child"></div>
  </div>
</div>

【讨论】:

  • 在我接受你的(优秀的)答案之前,只是为了确保:如果没有 Javascript,真的没有办法做到这一点吗?一些神秘的方式来放置 hitbox div 使其位于内容的后面和前面? pointer-eventsz-index 的一些 CSS 魔法?正如我所说,我在 Elm 中执行此操作,为了实施您的解决方案,我必须更改一些核心库。
  • @user2244484:我想不出用 CSS 来做到这一点的方法,因为您希望点击在子元素上注册。但是上面的elm版本不会很复杂吧?
  • 会的,或者在我找到this library 之前我是这么想的。这可能避免我不得不编写本机 javascript 代码。不过,我还没有尝试过。谢谢。
【解决方案2】:

使用停止传播https://developer.mozilla.org/en/docs/Web/API/Event/stopPropagation

将阻止进一步传播..

var container = document.getElementById("container");
var container_text = document.getElementById("content_text");
var container_child = document.getElementById("content_child");

container.addEventListener("click", function () {
    console.log('this is the container');
});

container_text.addEventListener("click", function () {
    console.log('this is the container_text');
});
container_child.addEventListener("click", function (event) {
  event.stopPropagation();
    console.log('this is the container_child');
});
#container {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: blue;
}

#content {
  position: absolute;
  width: 90%;
  height: 90%;
  background-color: yellow;
}

#content_child {
  position: absolute;
  width: 30px;
  height: 30px;
  left: 150px;
  top: 150px;
  background-color: red;
}
<div id="container">
  <div id="content">
    <div id="content_text">Hello</div>
    <div id="content_child"></div>
  </div>
</div>

【讨论】:

  • 与其他答案一样,它已包含在 OP 中。这是我目前的解决方案,但我不喜欢分别为每个孩子禁用一堆事件。
【解决方案3】:

尝试e.stopPropagation(),它会在查找子事件时阻止父母点击。

$(document).ready(function() {

  $("#container").click(function(e) {
    alert("#container clicked");
    e.stopPropagation();
  });

  $("#content_text").click(function(e) {
    e.stopPropagation();
    alert("#content_text clicked");
  });

  $("#content").click(function(e) {
    e.stopPropagation();
    alert("#content clicked");
  });

});
#container {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: blue;
}

#content {
  position: absolute;
  width: 90%;
  height: 90%;
  background-color: yellow;
}

#content_text {
  background: grey;
}

#content_child {
  position: absolute;
  width: 30px;
  height: 30px;
  left: 150px;
  top: 150px;
  background-color: red;
  color: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
  <div id="content">
    <div id="content_text">Hello</div>
    <div id="content_child">hi</div>
  </div>
</div>

https://codepen.io/anon/pen/zZbYvp

//codepen.io/anon/pen/zZbYvp

【讨论】:

  • 我在 OP 中说过我尝试停止传播以及为什么我不喜欢这种方法。
猜你喜欢
  • 2021-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
  • 1970-01-01
  • 2016-06-07
相关资源
最近更新 更多