Vanilla JS事件委托 - 处理目标元素的子元素

32

我正在尝试使用原生JS进行事件委托。我的容器里有一个按钮,就像这样:

<div id="quiz">
    <button id="game-again" class="game-again">
        <span class="icon-spinner icon"></span>
        <span>Go again</span>
    </button>
</div>

按照David Walsh的详细说明,我正在向按钮的祖先元素添加事件处理程序,如下所示:

this.container.addEventListener('click', function(e){
    if (e.target && e.target.id == 'game-again') {
        e.stopPropagation();
        self.publish('primo:evento');
    }
});

this.container 是 #quiz 元素时,这段代码有效。但是有一半的时间点击事件的目标是按钮内的一个 span 元素,所以我的事件处理程序不会被调用。怎样才能解决这种情况呢?


2
你需要支援哪些瀏覽器? - Benjamin Gruenbaum
IE9+以及主要的现代浏览器 - And Finally
只要获取未加前缀的版本并使用matches.call(e.target,"#game-again,#game-again *"),那么您就可以使用.matches了 - 有关更多详细信息,请参见我的答案。 - Benjamin Gruenbaum
1
如果您想要一个更通用的事件委托实现,带有一些方便的增强功能,仍然是纯JavaScript,请查看 Oxydizr。它使用 HTML5 数据属性,因此您可以完全将行为与样式分离:<button data-action="tasks.remove">X</button>。并且您可以从 data-action-params HTML5 数据属性中传递自定义数据给每个操作处理程序。 - Greg Burghardt
谢谢 Greg,我会去看看。 - And Finally
2个回答

46

更新的浏览器

更新的浏览器支持.matches

this.container.addEventListener('click', function(e){
    if (e.target.matches('#game-again,#game-again *')) {
        e.stopPropagation();
        self.publish('primo:evento');
    }
});

您可以通过以下方式获取非前缀版本:

var matches = document.body.matchesSelector || document.body.webkitMatchesSelector || document.body.mozMatchesSelector || document.body.msMatchesSelector || document.body.webkitMatchesSelector

然后对于更多浏览器使用.apply(仍支持IE9+)。

旧版浏览器

假设您需要支持旧版浏览器,您可以遍历DOM:

function hasInParents(el,id){
    if(el.id === id) return true; // the element
    if(el.parentNode) return hasInParents(el.parentNode,id); // a parent
    return false; // not the element nor its parents
}

然而,这将遍历整个DOM树,你需要在委托目标处停止:

function hasInParentsUntil(el,id,limit){
    if(el.id === id) return true; // the element
    if(el === limit) return false;
    if(element.parentNode) return hasInParents(el.parentNode,id); // a parent
    return false; // not the element nor its parents
}

这将使您的代码变为:

this.container.addEventListener('click', function(e){
    if (hasInParentsUntil(e.target,'game-again',container)) { // container should be 
        e.stopPropagation();                                  // available for this
        self.publish('primo:evento');
    }
});

1
太棒了!谢谢Benjamin。 - And Finally
谢谢,我按照你的建议使用了未添加前缀的版本,并将我的条件设置为“if (matches.apply(e.target, ['#game-again,#game-again *']))”,现在可以正常工作了。 - And Finally
2
你在示例中两次输入了 document.body.matchesSelector - thdoan
3
@Tomas,答案已经写在回答问题中了。我们期望读者能够运用自己的批判性思维技能来确定哪些部分对他们自己的需求是相关的。如果您认为可以写出更好的答案,我鼓励您这样做。 - zzzzBov
“*” 在 e.target.matches("#game-again *") 中的作用是什么?我只知道它允许匹配具有该 ID 的父元素。我在哪里可以阅读更多相关信息? - Ginger and Lavender
显示剩余3条评论

11

替代方案:

MDN:Pointer 事件

将类(.pointer-none)添加到所有嵌套的子元素上。

.pointer-none {
  pointer-events: none;
}

你的标记变得

<div id="quiz">
    <button id="game-again" class="game-again">
        <span class="icon-spinner icon pointer-none"></span>
        <span class="pointer-none">Go again</span>
    </button>
</div>

如果将指针设置为none,则无法在这些元素上触发点击事件。

https://css-tricks.com/slightly-careful-sub-elements-clickable-things/


另请参阅:https://stackoverflow.com/a/48683414/ - Max

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