点击元素的子元素需谨慎

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费额度!

假设您想将点击处理程序附加到 <button> 元素。您几乎肯定需要这样做,因为在 <form> 元素之外,按钮如果没有 JavaScript 的帮助是不会有任何作用的。您可以使用如下代码来实现:

var button = document.querySelector("button");
button.addEventListener("click", function(e) {
  // button was clicked
});

但这并没有使用事件委托。

事件委托是指您不将点击处理程序直接绑定到元素本身,而是绑定到 DOM 树中更高层的元素。这样做的目的是,您可以随时在其中移除和插入新的 DOM 元素,而无需担心事件会被销毁并需要重新绑定。

假设我们的按钮包含一个齿轮图标

<button>
  <svg>
    <use xlink:href="#gear"></use>
  </svg>
</button>

并且我们通过监视 document 元素上的点击来绑定它

document.documentElement.addEventListener("click", function(e) {
  
});

我们如何知道点击是否发生在按钮上?我们可以使用事件的目标来判断

document.documentElement.addEventListener("click", function(e) {
  console.log(e.target);
});

这里就变得有点棘手了。在这个例子中,即使用户点击按钮上的某个位置,根据他们点击的具体位置,e.target 可能是:

  • 按钮元素
  • svg 元素
  • use 元素

所以,如果您希望能够像这样操作:

document.documentElement.addEventListener("click", function(e) {
  if (e.target.tagName === "BUTTON") {
    // may not work, because might be svg or use
  }
});

不幸的是,它不会那么简单。无论您检查类名、ID 还是其他任何内容,您期望的元素本身都可能是不正确的。

有一个非常不错的 CSS 修复方案... 如果我们确保按钮内部没有任何元素具有 pointer-events 属性,则按钮内部的点击将始终针对按钮本身

button > * {
  pointer-events: none;
}

这也能防止其他 JavaScript 代码阻止事件冒泡到按钮本身(或更高层)的情况。

document.querySelector("button > svg").addEventListener("click", function(e) {
  e.stopPropagation();
  e.preventDefault();
});

document.querySelector("button").addEventListener("click", function() {
  // If the user clicked right on the SVG, 
  // this will never fire
});