child 和 parent 都是可点击的(child 可以是带有 jQuery 单击事件的链接或 div)。当我点击孩子时,如何只触发父点击事件而不触发子事件?
如何仅在单击子项时触发父级单击事件
DOM 事件阶段
活动分为三个阶段:
- 捕获:第一阶段是“捕获”,在此阶段调用事件处理程序,从 开始
<window>
并向下移动到事件目标的后代。 - 目标:第二阶段是调用目标上的事件侦听器时的“目标”阶段。
- 冒泡:第三个阶段是“冒泡”,首先监听目标的父级的处理程序首先被调用,然后逐步调用该元素的祖先。
事件也有一个“默认动作”,它发生在冒泡阶段之后。默认动作是通常发生为是事件的目标的种类元素的指定类型的事件(浏览器定义的动作例如在浏览器导航到href
的<a>
在一个click
,而click
在另一种类型的元件的意志有不同的默认操作)。
在DOM级别3事件草案有一个图,以图形方式显示事件通过DOM如何传播:
图片版权 © 2016万维网联盟,(麻省理工学院、ERCIM、京王、北航)。http://www.w3.org/Consortium/Legal/2015/doc-license(许可证允许使用)
有关捕获和冒泡的更多信息,请参阅:“什么是事件冒泡和捕获? ”;在DOM Level 3的草稿活动; 或W3C DOM4:事件
防止事件影响孩子
对于您想要的,要在父事件之前获取事件并阻止子事件发生,您必须在捕获阶段接收事件。一旦你在捕获阶段收到它,你必须阻止事件传播到 DOM 树中较低元素上的任何事件处理程序,或者在冒泡阶段注册为侦听的元素(即元素/阶段上的所有侦听器)在您的听众之后被事件访问)。您可以通过调用event.stopPropagation()
.
在捕获阶段接收事件
添加带有 的侦听器时addEventListener(type, listener[, useCapture])
,您可以将useCapture
参数设为true
。
引用 MDN:
[
useCapture
is] 一个布尔值,表示这种类型的事件将被分派到注册的侦听器,然后再分派到 DOM 树中它下面的任何 EventTarget。通过树向上冒泡的事件不会触发指定使用捕获的侦听器。事件冒泡和捕获是传播在嵌套在另一个元素中的元素中发生的事件的两种方式,当两个元素都为该事件注册了句柄时。事件传播模式决定了元素接收事件的顺序。有关详细说明,请参阅 DOM 级别 3 事件和 JavaScript 事件顺序。如果未指定,则useCapture
默认为 false。
防止其他处理程序获取事件
event.preventDefault()
用于防止默认动作(例如防止浏览器导航到href
的<a>
在一个click
)。[这在下面的示例中使用,但没有实际效果,因为文本没有默认操作。在这里使用它是因为在大多数情况下,当您添加单击事件处理程序时,您希望阻止默认操作。因此,养成这样做的习惯是个好主意,只是在您知道不想这样做时才这样做。]event.stopPropagation()
用于防止稍后在任何事件阶段中元素上的任何处理程序接收事件。它不会阻止调用当前元素和阶段上的任何其他处理程序。它不会阻止默认操作的发生。event.stopImmediatePropagation()
: 相同元素和阶段上的处理程序按添加顺序调用。除了与 具有相同的效果之外event.stopPropagation()
,还event.stopImmediatePropagation()
可以防止同一元素和事件阶段上的任何其他处理程序接收事件。它不会阻止默认操作的发生。鉴于此问题的要求是防止事件传播给孩子,我们不需要使用 this,但可以这样做而不是使用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 事件模型不支持事件捕获而只支持事件冒泡”
当您知道没有任何子元素是可交互的时,在某些情况下可能有用的另一个选项是pointer-events: none
在您的 css ( link ) 中设置。我通常将它应用于我想要捕获交互的元素的所有子元素。像这样:
#parentDiv * {
pointer-events: none
}
请注意*
,声明该规则适用于 的所有子项parentDiv
。
- 防止
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 的回答。)
为了让这里的生活变得非常简单和轻松,我在
与此类似的父节点上使用
target_image.addEventListener('drop',dropimage,true);
这将启用父子祖先关系,并且将为父子关系调用相同的事件。
要使事件只为父级调用,请在事件处理程序中使用以下代码片段。第一行
event.stopPropagation();
event.preventDefault();
您可以在元素上使用 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>