[英]A function called by a click event listener is being invoked without clicking, but only on second run through
这是一个谜题:
我正在尝试创建一个简单的选择你自己的路径游戏,它从本地 JSON 文件中获取其内容。 运行时,它会将每个场景(JSON 文件中的“部分”)分解为段落,并将第一段添加到 DOM,并在其周围的 div 上附加一个事件侦听器。 每次用户单击“容器”时,都会从该部分添加另一个段落,直到显示所有段落。
之后,如果用户再次单击“容器”,则会添加三个选项,每个选项都附加了自己的事件侦听器。 单击这些选项之一时,场景(或“部分”)将更改为选定的选项。 HTML“容器”被清空,过程通过添加新场景(部分)的第一段再次开始。
问题是它对第一部分和选项工作正常,但是在用户单击选项后,它加载了新场景的两段而不是一段。 它应该加载一个段落并等待点击事件,但它实际上加载了一秒钟。 第二段是通过 readyToUpdate 函数加载的,该函数只能由事件侦听器(尚未触发)调用。
{
"Section_1": {
"Content": "Dark corner paragraph one<>Dark corner paragraph two<>Dark corner paragraph three",
"Choices": [
"Go to the garden<>24",
"Go to the terrace<>95",
"Go to the beach<>145"
]
},
"Section_24": {
"Content": "Garden paragraph one<>Garden paragraph two<>Garden paragraph three",
"Choices": [
"Go to the dark corner<>1",
"Go to the terrace<>95",
"Go to the beach<>145"
]
},
"Section_95": {
"Content": "Terrace paragraph one<>Terrace paragraph two<>Terrace paragraph three",
"Choices": [
"Go to the dark corner<>1",
"Go to the garden<>24",
"Go to the beach<>145"
]
},
"Section_145": {
"Content": "Beach paragraph one<>Beach paragraph two<>Beach paragraph three",
"Choices": [
"Go to the dark corner<>1",
"Go to the garden<>24",
"Go to the terrace<>95"
]
}
}
<div id="container"></div>
fetch("js/contentFile.json")
.then(response => response.json())
.then(json => initialSetup(json));
let narrativeText;
let gameData = {
section: "",
paraCount: 0,
choices: [],
choiceText: [],
choiceDest: [],
paragraphs: "",
paraSections: []
};
function updateParagraphs(){
console.log("Enter updateParagraphs");
let container = document.getElementById("container");
let node = document.createElement("p");
let textnode =
document.createTextNode(gameData.paraSections[gameData.paraCount]);
node.appendChild(textnode);
container.appendChild(node);
container.addEventListener('click', readyToUpdate, {once: true});
console.log(gameData.paraCount + " is less than " +
gameData.paraSections.length);
console.log("Exit updateParagraphs");
console.log(gameData);
}
function readyToUpdate(e) {
console.log("Enter readyToUpdate");
console.log(e);
gameData.paraCount ++;
let container = document.getElementById("container");
update();
console.log("Exit readyToUpdate");
console.log(gameData);
}
function choiceUpdater(e){
console.log("Enter choiceUpdater");
let choiceNumber = e.target.id.split("_")[1];
gameData.section = "Section_" + gameData.choiceDest[choiceNumber];
document.getElementById("container").innerHTML = "";
gameData.paraCount = 0;
update();
console.log("Exit choiceUpdater");
console.log(gameData);
}
function addChoices() {
console.log("Enter addChoices");
gameData.choices = narrativeText[gameData.section].Choices;
console.log(gameData.choices);
for (let i=0; i<gameData.choices.length; i++){
let choice = document.createElement("h4");
let choicesSplit = gameData.choices[i].split("<>");
gameData.choiceText[i] = choicesSplit[0];
gameData.choiceDest[i] = choicesSplit[1];
let choiceTextNode = document.createTextNode(gameData.choiceText[i]);
choice.appendChild(choiceTextNode);
choice.setAttribute("id", "choice_" + i);
choice.addEventListener("click", choiceUpdater, {once: true});
document.getElementById("container").appendChild(choice);
}
console.log("Exit addChoices");
console.log(gameData);
}
function update() {
console.log("Enter update");
gameData.paragraphs = narrativeText[gameData.section].Content;
gameData.paraSections = gameData.paragraphs.split("<>");
if (gameData.paraCount < gameData.paraSections.length) {
updateParagraphs();
}
else {
addChoices();
}
console.log("Exit update");
console.log(gameData);
}
function initialSetup(data) {
console.log("Enter initial setup")
narrativeText = data;
gameData.section = "Section_1";
gameData.paraCount = 0;
update();
console.log("Exit initial setup");
console.log(gameData);
};
更新:当我如下更改 updateParagraphs 函数时,它可以工作。 但是从 Chrome 开发人员的性能来看,监听器的数量不断增加。 我使用的是 Chrome 80,所以它应该使用“once”属性删除它们,但即使我使用 removeEventListener() 手动删除侦听器,我也遇到了相同的初始问题。
function updateParagraphs(){
console.log("Enter updateParagraphs");
let container = document.getElementById("container");
let node1 = document.createElement("div");
node1.setAttribute("id", "innerContainer");
document.getElementById("container").appendChild(node1);
let innerContainer = document.getElementById("innerContainer");
let node = document.createElement("p");
let textnode =
document.createTextNode(gameData.paraSections[gameData.paraCount]);
node.appendChild(textnode);
innerContainer.appendChild(node);
innerContainer.addEventListener('click', readyToUpdate, {once: true});
}
function updateParagraphs(){
...
// container.addEventListener('click', readyToUpdate, {once: true});
setTimeout(function () {
container.addEventListener('click', readyToUpdate, {once: true});
});
}
我认为 setTimeout 来打破调用堆栈似乎有效,但我无法解释原因。
我相信正在发生的事情是,当您单击一个选项并调用该选项单击侦听器时,您将通过update()
和updateParagraphs()
并向容器添加一个新的单击侦听器。 但与此同时,单击事件正在传播到容器,并被您刚刚添加的新容器侦听器捕获。
所以你有两个选择:第一个,正如@wth193 所建议的,是使用 setTimeout() 在下一个滴答声中添加容器侦听器。
function updateParagraphs(){
// ...
// container.addEventListener('click', readyToUpdate, {once: true});
setTimeout(function () {
container.addEventListener('click', readyToUpdate, {once: true});
});
}
在我看来,一个更干净(更清晰)的解决方案是使用Event.stopPropagation() 。
function choiceUpdater(e){
console.log("Enter choiceUpdater");
e.stopPropagation();
// ...
}
这样,单击事件仅被选择元素捕获,而不是被容器捕获。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.