举个例子,比如一个 button 元素中包含一个齿轮图标:

<button>
  <svg>
    <use xlink:href="#gear"></use>
  </svg>
</button>
1
2
3
4
5

当用户点击齿轮图标,必然要触发 click 事件,但你并不会直接绑定事件到 svg 或 use 元素上,而是绑定到它们的父元素 button 上。即:

document.querySelector('button').addEventListener('click', function (e) {
    console.log('点击了按钮');
    // 查看 事件具体是发生在哪个元素上面
    console.log(e.target);
})
1
2
3
4
5

这时会产生一个问题,根据用户点击的位置,e.target 可能是下面三种情况:

BUTTON 元素 SVG 元素 USE 元素 实际的情况是这样的

29-17-YhrexO

我们真正的意图是,只要点击是发生在按钮上面,不论是按钮的哪个位置,我们都应视为按钮被点击了。 嗯,简单,我们再改一下,这样写:

document.documentElement.addEventListener('click', function (e) {
  if (['BUTTON', 'SVG', 'USE'].includes(e.target.tagName.toUpperCase())) {
    // 点击的是按钮
  }
})
1
2
3
4
5

这样似乎没什么问题,也确实可以达到目的,但看上去总是有些别扭。因为这种情况对于最上层的 document 来说,得知道每个子元素的情况,本来我只需要关心离我最近的 button 元素就可以了。

根据 OOP 对内封装的思想,button 元素内部的事情应该在内部消化掉,其子元素对外不可见,应该只暴露 button 元素本身。依据这个思想和事件冒泡的特点,我们就有了比较好的解决办法:只需要禁止 button 内部元素的事件响应(包括事件冒泡)而只允许 button 元素本身的事件发生就行。有两种方式可以实现这个目的。

一种是使用 CSS 禁止 button 内部元素的事件响应:

# pointer-events

button > * {
  pointer-events: none;
}
1
2
3

# stopPropagation

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

document.querySelector('button').addEventListener('click', function (e) {
  console.log(e.target.tagName)
})
1
2
3
4
5
6
7
8

# ParentNode

查询点击的父节点判断是不是在button节点内部

# 参考

https://www.cnblogs.com/fehoney/p/12962280.html

上次更新: 5/28/2020, 11:33:41 AM