在动态创建的元素上添加事件监听器

59

是否可以在所有动态生成的元素上添加事件监听器(Javascript)?由于不是该页面的所有者,所以我无法以静态方式添加侦听器。

对于页面加载时创建的所有元素,我使用以下代码:

doc.body.addEventListener('click', function(e){
//my code
},true);

我需要一种方法,在页面上出现新元素时调用此代码,但我不能使用jQuery(委托、on等在我的项目中无法工作)。我该怎么做?

8个回答

89

看起来你需要采用委托策略而不是回退到库。我在这里发布了一些示例代码:http://jsfiddle.net/founddrama/ggMUn/

要点是使用event对象上的target来查找你感兴趣的元素,并作出相应的响应。类似于:

document.querySelector('body').addEventListener('click', function(event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    // do your action on your 'li' or whatever it is you're listening for
  }
});

注意事项!这个示例代码只包括标准兼容的浏览器(例如IE9+以及每个其他版本)。如果您需要支持“旧版IE”的attachEvent,那么您需要提供自己的包装器来包含适当的本地函数。有很多关于此问题的好讨论;我喜欢Nicholas Zakas在他的书《Web开发者专业JavaScript》中提供的解决方案。


2
@JianWeihang,你能澄清一下你的问题吗?你不想直接将元素应用于 body 元素(例如作为 onclick 属性),否则会有破坏已经存在的监听器的风险。事件委托策略更加安全:不太可能干扰或覆盖元素上的其他监听器,并且不太可能使用每个元素的监听器来占用内存。 - founddrama
很好的答案,我在想难道不只是 document.body === document.querySelector('body') 吗?这就是为什么在两种情况下你总是将点击处理程序附加到唯一的 body 元素。 - drinchev
3
是的,这两个是等价的。在举例说明时,我更喜欢使用document.querySelector()版本,因为这样更容易概括 —— 也就是说,并不是每个元素都可以从文档中作为属性访问到。 - founddrama
如果我的事件是 focus,似乎不起作用。我该怎么办? - ColorWin
@ColorWin 你需要使用事件捕获 - Sebastian Simon

18

取决于您如何添加新元素。

如果您使用createElement添加,您可以尝试以下方法:

var btn = document.createElement("button");
btn.addEventListener('click', masterEventHandler, false);
document.body.appendChild(btn);

然后你可以使用masterEventHandler()来处理所有的点击事件。


1
我不是添加元素的页面的所有者,所以我不知道元素的插入方式。 - jenjis
我认为这个答案存在的问题是,当/如果底层元素被移除时,事件监听器仍将保留在内存中。这是因为btn的引用仍然存在。在这种情况下,所选择的答案是更好的答案。更多信息请参见这里 - Crayons

3
当你只需支持“现代”网络浏览器(不包括微软Internet Explorer)时,变异观察器是此任务的适当工具。
new MutationObserver(function(mutationsList, observer) {
    for(const mutation of mutationsList) {
        if (mutation.type === 'childList') {
            // put your own source code here
        }
    }
}).observe(document.body, {childList: true, subtree: true});

3
值得注意的是一个比较晦涩的问题是:如果一个元素的z-index属性设置为-1或更小,你可能会认为监听器没有被绑定,实际上它已经被绑定了,但浏览器认为你点击了一个更高z-index的元素。在这种情况下,问题并不在于监听器没有被绑定,而是它无法获取焦点,因为其他东西(例如,可能是一个隐藏的元素)在你的元素上方,获取焦点的是其他东西(也就是说:事件没有被触发)。幸运的是,你可以通过右键单击该元素,选择“检查”并检查你所点击的是否是被“检查”的内容来很容易地检测到这一点。我正在使用Chrome,我不知道其他浏览器是否也受到影响。但是,它很难找到,因为从功能上讲,它大多数方面都类似于监听器未被绑定的问题。我通过从CSS中删除以下行来解决了这个问题:z-index:-1;

2
我创建了一个小函数来添加动态事件监听器,类似于 jQuery.on()
它使用与已接受的答案相同的思路,只是使用Element.matches()方法来检查目标是否与给定选择器字符串匹配。
addDynamicEventListener(document.body, 'click', '.myClass, li', function (e) {
    console.log('Clicked', e.target.innerText);
});

你可以从 Github 上获取 这个项目

1
最终这个方法对我起作用了。我必须为动态生成的WooCommerce消息添加事件。尝试了很多方法,但这是唯一有效的解决方案。非常感谢! - Dorji Tshering

1
将匿名任务委托给动态创建的HTML元素,使用event.target.classList.contains('someClass'):
  1. 返回true或false
  2. 例如:
let myEvnt = document.createElement('span');
myEvnt.setAttribute('class', 'someClass');
myEvnt.addEventListener('click', e => {
  if(event.target.classList.contains('someClass')){ 
    console.log(${event.currentTarget.classList})
}})
  1. 参考资料:https://gomakethings.com/attaching-multiple-elements-to-a-single-event-listener-in-vanilla-js/
  2. 好文推荐:https://eloquentjavascript.net/15_event.html#h_NEhx0cDpml
  3. MDN:https://developer.mozilla.org/en-US/docs/Web/API/Event/Comparison_of_Event_Targets
  4. 插入点:

-1
你可能想要看一下这个库:https://github.com/Financial-Times/ftdomdelegate,它被压缩为1.8K。
它用于绑定与给定选择器匹配的所有目标元素上的事件,无论在注册时是否存在DOM中都可以进行绑定。
您需要导入脚本,然后实例化它,如下所示:
  var delegate = new Delegate(document.body);
  delegate.on('click', 'button', handleButtonClicks);

  // Listen to all touch move
  // events that reach the body
  delegate.on('touchmove', handleTouchMove);

});



-15

使用 classList 属性一次绑定多个类

var container = document.getElementById("table");
container.classList.add("row", "oddrow", "firstrow");

事件监听器不是类。 - Quentin

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