如何在子元素被点击时仅触发父元素的点击事件

69

子元素和父元素都可以被点击(子元素可能是带有jQuery点击事件的链接或div)。当我点击子元素时,如何仅触发父元素的点击事件而不触发子元素的事件?


2
如果子元素永远不会被触发,为什么要在子元素上设置事件呢? - adeneo
你可以将子元素的点击事件绑定到父元素上。 - Redu
1
这篇文章可能会有所帮助:https://css-tricks.com/return-false-and-prevent-default/ - Linus Aronsson
2
请考虑包含一个示例来帮助演示您想要实现的内容。 - showdev
@LinusAronsson提出了一个很好的观点。你需要查看事件冒泡。这样,子事件可以对点击做出反应,但通过返回false将事件向上冒泡到父级,以便它也可以根据需要处理它。我建议你像showdev提到的那样包含更多信息,因为没有具体的示例,很难看出这个功能如何运作。 - Dan Sabin
任何直接附加在子元素上的事件都会优先执行(甚至在其父元素之前)。 - undefined
6个回答

189

DOM 事件阶段

事件有三个阶段:

  1. 捕获阶段:第一个阶段是“捕获”阶段,事件处理程序从 <window> 开始,依次向下通过后代元素,直到事件的目标元素。
  2. 目标阶段:第二个阶段是“目标”阶段,此时会调用目标元素上的事件监听器。
  3. 冒泡阶段:第三个阶段是“冒泡”阶段,它从目标元素的父级处理程序开始调用,逐步向上遍历祖先元素。

事件还有一个“默认行为”,它发生在冒泡阶段之后。默认行为是浏览器针对特定类型事件在目标元素上执行的预定义操作(例如,在单击 <a> 元素上的 click 事件时,浏览器会导航至链接的 href,而在其他类型元素上点击可能会有不同的默认行为)。

DOM Level 3 Events draft中有一张图表,用图形化的方式展示了事件如何在DOM中传播:

使用DOM事件流在DOM树中分派事件的图形表示
版权所有 © 2016 万维网联盟, (麻省理工学院, 欧洲研究中心协会, 慶應義塾大学, 北京航空航天大学). http://www.w3.org/Consortium/Legal/2015/doc-license (根据许可证允许使用)

如果想了解有关事件捕获和冒泡的更多信息,请参见: "什么是事件冒泡和捕获?"; DOM Level 3 Events draft; 或W3C DOM4: Events

防止事件传递到子元素

如果您想在子元素上发生事件之前在父元素上接收并阻止事件,您需要在捕获阶段接收事件。一旦您在捕获阶段接收到它,就必须阻止事件传播到DOM树中较低的任何元素上的任何事件处理程序,或者已注册以在冒泡阶段侦听的元素/阶段上的所有侦听器(即在您的侦听器之后访问事件的所有侦听器)。您可以通过调用event.stopPropagation()来实现此目的。

在捕获阶段接收事件

当使用 addEventListener(type, listener[, useCapture]) 添加监听器时,您可以将 useCapture 参数设置为 true
引用 MDN 的说法:

[useCapture] 是一个布尔值,指示在事件被分派到 DOM 树中的任何 EventTarget 之前,将该类型的事件分派给注册的侦听器。向上冒泡的事件不会触发指定使用捕获的侦听器。事件冒泡和捕获是传播在嵌套在另一个元素中的元素中发生的事件的两种方式,当这两个元素都为该事件注册了处理程序时。事件传播模式确定元素接收事件的顺序。有关详细说明,请参阅 DOM Level 3 Events 和 JavaScript Event order。如果未指定,则 useCapture 默认为 false。

防止其他处理程序获取事件

  • event.preventDefault()用于阻止默认操作(例如防止浏览器在单击时导航到<a>href)。[在下面的示例中使用了它,但实际上没有任何效果,因为文本没有默认操作。这里使用它是因为大多数情况下,当您添加单击事件处理程序时,您希望阻止默认操作。因此,养成这样的习惯是一个好主意,只有在您知道不想这么做时才不这样做。]
  • event.stopPropagation()用于防止后续事件阶段中元素上的任何处理程序接收事件。它不会防止当前元素和阶段上的任何其他处理程序被调用。它不会防止默认操作发生。
  • event.stopImmediatePropagation():同一元素和阶段上的处理程序按添加顺序调用。除具有与event.stopPropagation()相同的效果外,event.stopImmediatePropagation()还会防止同一元素和事件阶段上的任何其他处理程序接收事件。它不会防止默认操作发生。考虑到此问题的要求是防止事件传播到子级,我们不需要使用它,但可以使用它代替event.stopPropagation()。但请注意,同一元素上的侦听器按添加顺序调用。因此,event.stopImmediatePropagation()不会防止那些在您的侦听器之前添加到相同元素和阶段上的侦听器接收事件。

例子

在下面的例子中,事件监听器被放置在父元素和子元素的<div>上。只有放置在父元素上的监听器接收到事件,因为它在捕获阶段先于子元素接收到事件,并且执行了event.stopPropagation()

var parent=document.getElementById('parent');
var child=document.getElementById('child');
var preventChild=document.getElementById('preventChild');

parent.addEventListener('click',function(event){
    if(preventChild.checked) {
        event.stopPropagation();
    }
    event.preventDefault();
    var targetText;
    if(event.target === parent) {
        targetText='parent';
    }
    if(event.target === child) {
        targetText='child';
    }
    console.log('Click Detected in parent on ' + targetText);
},true);
                      
child.addEventListener('click',function(event){
    console.log('Click Detected in child (bubbling phase)');
});

child.addEventListener('click',function(event){
    console.log('Click Detected in child (capture phase)');
},true);
<input id="preventChild" type="checkbox" checked>Prevent child from getting event</input>
<div id="parent">Parent Text<br/>
  <div id="child" style="margin-left:10px;">Child Text<br/>
  </div>
</div>

jQuery

jQuery不支持在事件上使用捕获。有关原因的更多信息,请参见:"为什么 jQuery 事件模型不支持事件捕获,只支持事件冒泡"


4
这是一个很好的回答。问题的发起者可能希望阅读并采纳它。 - Dan Sabin
你如何使用IE的捕获element['onclick'] = function(e) {}; - Cagatay Ulubay

58

在某些情况下,如果您知道没有子元素是交互式的,则可以在CSS中设置pointer-events:none,这对于此选项可能会有用(link)。我通常将其应用于我想捕获交互的元素的所有子元素。像这样:

#parentDiv * {
    pointer-events: none
}

注意*,表示该规则适用于parentDiv的所有子元素。

1
有趣的解决方案(+1)。然而,它的缺点是阻止了所有其他类型的指针事件,而不仅仅是点击事件,无法传递给子元素。 - Makyen
1
这个解决方案很简单,完全符合我的需求,感谢您解决了我遇到的问题。 - Andy
惊人的解决方案。简短、快速,立即解决了我的问题。谢谢 :) - di3
@PsiKai 我不明白这怎么会导致只有在单击子元素时才触发父元素的单击事件,这正是问题所问的。我想说的是CSS没有能力仅禁用单击事件。它要么全部禁用指针事件,要么全部启用。另一方面,肯定有用途,可以选择性地在某些子元素上启用 pointer-events - Makyen
1
这解决了我遇到的一个无关问题。在移动端(触摸屏)上,如果用户在子元素内点击,则单击事件不会冒泡并调用父侦听器。在桌面端(鼠标),事件按预期冒泡到监听器。我看到的另一个解决方案是使用touchend事件,但如果你滑动屏幕,该事件将意外地被触发。在所有子元素上设置pointer-events:none解决了我的问题,并且现在它按预期工作。 - Mike
显示剩余6条评论

6
  • 防止子元素接收到父元素的点击事件:children
parent.addEventListener('click',function(e){
  e.stopPropagation();
  console.log('event on parent!')
},true);

(请注意,第二个参数是true

  • 防止parent接收自身或其子元素的点击事件:
parent.addEventListener('click',function(e){
  e.stopPropagation();
  console.log('event on parent or childs!', e.target.closest('.parent_selector'))
});
  • e.stopPropagation 的意思是阻止事件传递给层次结构中的下一个元素。
  • 第二个参数 (useCapture) 是一个标志,意味着反转接收事件的顺序。(使用 capture 阶段而不是 bubble 阶段。) 这意味着如果将其设置为 true,则父元素将先接收点击事件,然后才是子元素。(通常情况下子元素会先接收到事件。)

(详细解释请见 @Makyen 的回答。)


0
你可以在HTML文件中使用 $event.stopPropagation()。

(click)="openAttendeesList(event.id,event.eventDetailId,event.eventDate) ; $event.stopPropagation();"


0

为了让生活变得更加简单和容易,我在这里
使用类似于此的父节点

target_image.addEventListener('drop',dropimage,true);

这将启用父子祖先关系,同一事件将为父级和子级调用。
要使事件仅为父级调用,请在事件处理程序中使用以下代码片段。第一行
event.stopPropagation();
event.preventDefault();

-3

您可以在元素上使用CustomEvents属性。

  • 创建一个事件对象,让子元素将事件分派到其父元素

在此处查看演示

document.getElementById('parent').onclick = function() {
  alert("you are clicking on the parent stop it");
}
document.getElementById('child').onclick = function(e) {
  alert('I am sending this event to my parent');
  event = new CustomEvent('click');
  document.getElementById('parent').dispatchEvent(event);

}
#parent {
  display: inline-block;
  width: 100px;
  height: 100px;
  border: solid black;
}

#child {
  border: solid red;
}
<div id=parent>
  <div id=child>I am a child</div>
</div>


这个问题特别询问的是“仅触发父级点击事件而不触发子事件”。以您的方式来做,子元素上的事件仍会被触发(并且任何其他监听器也将被调用)。 - Makyen
此外,除非您完全控制页面的HTML和所有JavaScript,否则使用element.onclick属性是一个坏主意。分配给该属性会替换已经定义的任何侦听器(只能在属性/属性上存在一个)。这可能会导致您无法完全控制的页面出现故障。在JavaScript中,更好的方法是使用addEventListener() - Makyen
我同意你的第二条评论,但不同意你的第一条评论。OP问题的标题是“如何仅在单击子元素时触发父元素的单击事件”...用户单击子元素会触发父元素...我知道你发布的答案...你的方法很受欢迎(我也使用),但我只是说明另一种方法。 - repzero
我同意标题不够具体。然而,我引用的内容直接来自问题。请看问题中的第二句和最后一句。您提出了一种替代方法,这在两个元素没有祖先<->后代关系的情况下可能很有用。 - Makyen
event.target在示例中引导我进一步研究“event”,非常有用的答案! - gonatee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接