如何为JavaScript生成的所有div元素添加事件监听器

4

我正在尝试创建一个复杂的项目:一个Youtube界面的克隆,并且我在JS方面遇到了问题。

思路

思路是:在滚动时,应该自动创建更多的 divs,类似于第一个(这部分工作得很好)。

问题

但是,当我想要为click添加事件监听器时,JavaScript无法处理后续 divs上的事件, 而实际上会自动生成50~100个具有相同类的其他 divs。因此,我认为.length可能存在问题。

这是演示网站,让您更好地了解问题:

https://laaouatni.github.io/yt-clone/

这是我认为问题所在的位置:

function createVideo() {
    let videoComponent = videoContainer.cloneNode(true);
    mainContainer.appendChild(videoComponent);
}

...

    <main>
        <div class="video-container">

           ...

        </div>
        <!-- here javascript will put other divs on scroll -->
    </main>

...

window.addEventListener("scroll", function() {
   let scrollPercentage = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;

    if (scrollPercentage > 70) {
        createVideo();
    }
}

let videoContainer = document.querySelector(".video-container");
let allVideoContainer = document.querySelectorAll(".video-container");

for (let index = 0; index < allVideoContainer.length; index++) {
    allVideoContainer[index].addEventListener("click", function() {
        console.log("clicked video N " + index);
    })
}

如果我点击手动编写的 div,则会执行 click 函数,但是对于生成的 divclick 不起作用。我不知道 DOM 是否没有更新或者我需要编写更多内容?在互联网上没有解决此问题的结果(希望能找到有经验的人,在这里帮助我)。

如果需要,所有代码均存储在 github 仓库中(以帮助我们)

https://github.com/Laaouatni/yt-clone


一个 YouTube 用户界面克隆 - 为什么? - Dai
1
你的 scroll 事件监听器需要被设置为 passive - Dai
@Dai,什么是“被动”的意思?(我是初学者) - Laaouatni Anas
请看:https://dev59.com/5loU5IYBdhLWcg3wM00H - Dai
事件委托还可以解决动态创建的 div 没有附加事件监听器的问题。https://dev59.com/-HI-5IYBdhLWcg3wwbZM - Andrew Lohr
3个回答

4
你的问题在于,你在页面加载时最初添加了事件监听器。这意味着你的 for 循环只会遍历页面初始加载时存在的 video-container 元素。在你添加事件监听器之后创建的任何 video-container 元素都没有附加事件监听器。即使你在具有使用 addEventListener() 附加的事件监听器的元素上使用 .cloneNode(),事件监听器也不会被复制到新元素中,因为 addEventListener() 事件处理程序不会被复制。
一个想法是每次调用 createVideo() 时向 videoComponent 添加一个新的事件监听器。但问题是这可能会导致你向页面添加许多事件监听器,从而降低网页性能。
更好的方法是使用事件委托。事件委托的思想是将事件监听器添加到一个父元素上(在您的情况下为<main>元素)。当您嵌套在父<main>元素下的一个video-container元素被点击时,单击事件将"冒泡"<main>元素,触发父<main>元素上的单击事件监听器。在附加到父级<main>元素的事件侦听器中,可以使用e.target获取对最初单击的video-container元素的引用,其中e是提供在事件侦听器回调函数中的Event对象。由于单击事件监听器位于父级main容器上,因此任何新创建的子元素的单击事件监听器都将起作用,因为来自子元素的事件将冒泡到父级主容器的事件处理程序。
为了在你的代码中实现事件委托,你可以移除添加单个事件监听器的for循环,而是将你的点击事件监听器添加到mainContainer元素上。当它的子元素被点击时,它会捕获冒泡到它的点击事件。
mainContainer.addEventListener("click", function(e) { // e = event object
  if (e.target && e.target.matches(".video-container")) {
    const clickedVideoContainer = e.target;
    // do stuff with `clickedVideoContainer`
  }
});

这里我们还检查了被点击的元素(e.target)是否实际上是一个视频容器元素,通过查看它是否matches()选择器.video-container。这是必需的,因为我们的mainContainer事件侦听器可以触发从其任何子级、孙子级等冒泡上来的任何点击事件。
正如@Dai在评论中指出的那样,您可能还希望在添加滚动事件侦听器时考虑使用passive选项以提高性能。

2

介绍

当您调用addEventListener时,只有存在的标签会接收该事件。未来尚不存在的标签将不会具有该事件。您需要以某种方式保证未来的元素也具有该事件。有多种方法可以实现此目的。

onclick

确保处理click事件的一种非常简单的方法是为标签指定onclick属性,如:

onclick="yourfunction

示例:

function foo(element) {
    alert([...element.parentNode.children].indexOf(element));
}

function addDiv() {
    let container = document.getElementById("container");
    container.innerHTML += `
        <p onclick="foo(this);">${container.children.length}</p>
    `;
};
<input type="button" onclick="addDiv()" value="Click Here!">
<div id="container">
</div>

动态添加事件监听器

有时候我们需要标记已经初始化过的项目,并且只为未初始化的项目添加事件监听器,随后再对它们进行初始化。

例如:

function foo(element) {
    alert([...element.parentNode.children].indexOf(element));
}

function addDiv() {
    let container = document.getElementById("container");
    container.innerHTML += `
        <p onclick="foo(this);">${container.children.length}</p>
    `;
    
    for (let item of document.querySelectorAll(".container > div:not(.initialized)")) {
        item.addEventListener("click", () => {
            foo(this);
            this.classList.add("initialized");
        });
    }
};
<input type="button" onclick="addDiv()" value="Click Here!">
<div id="container">
</div>

向HTML节点添加事件监听器

您可以简单地调用videoComponent.addEventListener

示例:

function foo(element) {
    alert([...element.parentNode.children].indexOf(element));
}

function addDiv() {
    let container = document.getElementById("container");
    let newNode = container.children[0].cloneNode(true);
    newNode.innerText = container.children.length;
    container.appendChild(newNode);
    newNode.addEventListener("click", function() {
        foo(this);
    });
};

document.getElementById("container").children[0].addEventListener("click", function() {
    foo(this);
});
<input type="button" onclick="addDiv()" value="Click Here!">
<div id="container">
    <p>0</p>
</div>

变异观察器

感谢 Mutation Observer for creating new elements

您可以使用变异观察器来检测标记创建并将事件附加到它。如果可能的话,应该避免使用它,更多时候您有一个更简单的解决方案,但是为了完整起见,这里是一个示例,取自上面分享的链接:

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

$("#foo").live("click",function(e) {
    e.preventDefault();
    $(this).append($("<div />").html("new div").attr("id","bar"));
});

// define a new observer
var obs = new MutationObserver(function(mutations, observer) {
    // look through all mutations that just occured
    for(var i=0; i<mutations.length; ++i) {
        // look through all added nodes of this mutation
        for(var j=0; j<mutations[i].addedNodes.length; ++j) {
            // was a child added with ID of 'bar'?
            if(mutations[i].addedNodes[j].id == "bar") {
                console.log("bar was added!");
            }
        }
    }
});

// have the observer observe foo for changes in children
obs.observe($("#foo").get(0), {
  childList: true
});

事件委托

最后但并非最不重要的,正如其他人已经提到的那样,您可以将事件委托给父节点,但在这种情况下,您需要找出事件的发起者,这是事件对象的 target 字段。

例如:

function foo(element) {
    alert([...element.parentNode.children].indexOf(element));
}

function addDiv() {
    let container = document.getElementById("container");
    container.innerHTML += `
        <p>${container.children.length}</p>
    `;
};

document.getElementById("container").addEventListener("click", function(event) {
    foo(event.target);
});
<input type="button" onclick="addDiv()" value="Click Here!">
<div id="container">
</div>


1
除了@Nick的答案之外,使用原生JS也是可以实现的,但如果有JQuery可用,它会使代码变得非常简单且易读。
$(document).on('click','.video-container',function(e){
   //your desired actions
})

第一个参数是事件类型(点击,悬停等)。您可以使用任何常规选择器作为第二个参数。


好的,谢谢。我给你点赞了,但是我想把悬赏给@Nick,因为他真的很帮忙,写了一个超级详细的答案,我认为他值得得到这个奖励。 :) 干得好! - Laaouatni Anas
1
当然,毕竟他是第一个回答的。我只是对他原来的答案进行了一点小改进。很高兴能帮忙,干杯! - Skywarth

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