我正试图创建一个复杂的项目:Youtube UI克隆,但JS 出现了问题
这个想法
其想法是:在滚动时,应该自动创建更多的div,类似于第一个(这很好)
问题
但当我想为click
设置一个事件监听器时,JavaScript不处理后续div上的事件,但在现实中,会自动生成50到100个其他div(具有相同的类)所以我认为.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
函数,但对于生成的div,click
不起作用。我不知道DOM是不更新还是应该写更多的东西?在互联网上没有解决问题的结果(我希望找到一个有经验的人,他可以在这里帮助我)
如果您需要,针对所有代码github repo(帮助我们)
https://github.com/Laaouatni/yt-clone
您的问题是在页面加载时最初添加事件侦听器。这意味着添加事件侦听器的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
)是否是选择器.video-container
的matches()
来检查它实际上是视频容器元素。这是必要的,因为我们的mainContainer
事件侦听器可以触发任何子代、孙代等弹出的点击事件。
正如@Dai在评论中所指出的,在添加滚动事件侦听器时,您可能还需要考虑使用passive
选项来提高性能。
简介
当您调用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>
突变观察者
由突变观察者创建新元素提供
你可以使用突变观察者来检测标签的创建并将事件附加到它上。如果可能的话,应该避免这种情况,通常你有一个更简单的解决方案,但为了完整性,这里有一个来自上面共享的链接的例子:
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>
除了@Nick的回答之外,是的,它可以用vanilla JS来完成,但如果JQuery可用,它会使它变得超级简单,可读性更强。
$(document).on('click','.video-container',function(e){
//your desired actions
})
第一个参数是事件类型(点击、悬停等)。第二个参数可以使用任何传统的选择器。