为什么我的输入单词的函数会产生乱码输出?
Why does my function that type words out produce garbled output?
在下面的代码中,我希望它获取所有 .auto-type
元素中的单词作为输入,然后开始自动逐个字符地输入它们。但是,我的代码不起作用,但它也不会在控制台中产生任何错误。我错过了什么?
window.onload = () => {
let elements = document.getElementsByClassName("auto-type");
for (element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
let charIndex = 0;
const typing = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typing, 100);
charIndex++;
}
};
typing();
}
};
body {
background-color: black;
color: lime;
font-family: consolas, monospace;
}
<span class="auto-type code">hello!</span>
<br />
<span class="auto-type code">some text</span>
<br />
<div class="auto-type">some other text</div>
<span class="auto-type code">here is a better text</span>
该代码有效,但因为它是异步的,它恰好试图同时全部输入。它没有意义的另一个原因是因为它还试图将其键入 same 元素。
调查问题
呈现后,您会在页面的 HTML 中看到:
<span class="auto-type code">h</span>
<br>
<span class="auto-type code">s</span>
<br>
<div class="auto-type">s</div>
<span class="auto-type code">heooelmmrleeeo !toietsxh tear bteetxtter text</span>
关注最后一行,您可以看到每条消息都已键入到最后一个元素中。通过用数字替换原始消息中的每个字符,可以使这一点更加明显:
<span class="auto-type code">111111</span>
<br />
<span class="auto-type code">222222222</span>
<br />
<div class="auto-type">333333333333333</div>
<span class="auto-type code">444444444444444444444</span>
呈现:
<span class="auto-type code">1</span>
<br>
<span class="auto-type code">2</span>
<br>
<div class="auto-type">3</div>
<span class="auto-type code">412341234123412341234234234234343434343434444444</span>
所以最后一行是这样构建的:
heooelmmrleeeo !toietsxh tear bteetxtter text
│││└ the 'e' from "here" in the fourth element
││└ the 'o' from "some" in the third element
│└ the 'o' from "some" in the second element
└ the 'e' from "hello" in the first element
发生这种情况的原因是因为这个 for-loop,其中变量 element
的范围不是 for-loop,这意味着它被每个 typing
共享函数,并将引用 elements
:
中的最后一个元素
for (element of elements) { // <- this element variable is a global!
/* ... */
}
您可以通过简单地用 let
或 const
重新定义 element
变量来解决您的主要问题:
for (const element of elements) { // <- this element variable is now scoped to this for-loop
/* ... */
}
这个改动的效果可以看下面StackSnippet:
window.onload = () => {
let elements = document.getElementsByClassName("auto-type");
for (const element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
let charIndex = 0;
const typing = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typing, 100);
charIndex++;
}
};
typing();
}
};
body {
background-color: black;
color: lime;
font-family: consolas, monospace;
}
<span class="auto-type code">hello!</span>
<br />
<span class="auto-type code">some text</span>
<br />
<div class="auto-type">some other text</div>
<span class="auto-type code">here is a better text</span>
其他问题
由于 JavaScript 的性质,加载页面和所有依赖项可能需要一段时间。这意味着您的消息在页面加载时对用户可见,这可能不是您想要的。所以你应该用 CSS 隐藏消息,然后在你准备好时显示它们。
.auto-type {
visibility: hidden; /* like display:none; but still consumes space on the screen */
}
然后当您清空文本时,再次使元素可见:
const text = element.innerHTML.toString();
element.innerHTML = "";
element.style.visibility = "visible";
更正代码
您需要修改“输入”功能,以便在输入上一条消息之前不触发。我们可以通过将每个“键入”函数更新为 return 一个 Promise,然后将这些 Promise 链接在一起来做到这一点。
/**
* Types text into the given element, returning a Promise
* that resolves when all the text has been typed out.
*/
function typeTextInto(element, text, typeDelayMs = 100) {
return new Promise(resolve => {
let charIndex = 0;
const typeNextChar = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typeNextChar, typeDelayMs);
charIndex++;
} else {
resolve(); // finished typing
}
};
typeNextChar(); // start typing
});
}
window.onload = () => {
const elements = document.getElementsByClassName("auto-type");
const elementTextPairs = [];
// pass 1: empty out the contents
for (element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
element.style.visibility = "visible";
elementTextPairs.push({ element, text });
}
// pass 2: type text into each element
let chain = Promise.resolve(); // <- empty Promise to start the chain
for (elementTextPair of elementTextPairs) {
const { element, text } = elementTextPair; // unwrap pair
chain = chain // <- add onto the chain
.then(() => typeTextInto(element, text));
}
// basic error handling
chain.catch(err => console.error("failed to type all messages:", err));
};
这可以使用 async
/await
进一步清理,并将输入逻辑拆分到它自己的函数中。这显示在下面的工作 StackSnippet 中:
/**
* Types text into the given element, returning a Promise
* that resolves when all the text has been typed out.
*/
function typeTextInto(element, text, typeDelayMs = 100) {
return new Promise(resolve => {
let charIndex = 0;
const typeNextChar = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typeNextChar, typeDelayMs);
charIndex++;
} else {
resolve(); // finished typing
}
};
typeNextChar(); // start typing
});
}
window.onload = async () => {
try {
const elements = document.getElementsByClassName("auto-type");
const elementTextPairs = [];
// pass 1: empty out the contents
for (element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
element.style.visibility = "visible";
elementTextPairs.push({ element, text });
}
// pass 2: type text into each element
for (elementTextPair of elementTextPairs) {
const { element, text } = elementTextPair; // unwrap pair
await typeTextInto(element, text); // await in a for-loop makes the asynchronous work run one-after-another
}
} catch (err) {
// basic error handling
console.error("failed to type all messages:", err);
}
};
body {
background-color: black;
color: lime;
font-family: consolas, monospace;
}
.auto-type {
visibility: hidden;
}
<span class="auto-type code">hello!</span>
<br />
<span class="auto-type code">some text</span>
<br />
<div class="auto-type">some other text</div>
<span class="auto-type code">here is a better text</span>
在下面的代码中,我希望它获取所有 .auto-type
元素中的单词作为输入,然后开始自动逐个字符地输入它们。但是,我的代码不起作用,但它也不会在控制台中产生任何错误。我错过了什么?
window.onload = () => {
let elements = document.getElementsByClassName("auto-type");
for (element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
let charIndex = 0;
const typing = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typing, 100);
charIndex++;
}
};
typing();
}
};
body {
background-color: black;
color: lime;
font-family: consolas, monospace;
}
<span class="auto-type code">hello!</span>
<br />
<span class="auto-type code">some text</span>
<br />
<div class="auto-type">some other text</div>
<span class="auto-type code">here is a better text</span>
该代码有效,但因为它是异步的,它恰好试图同时全部输入。它没有意义的另一个原因是因为它还试图将其键入 same 元素。
调查问题
呈现后,您会在页面的 HTML 中看到:
<span class="auto-type code">h</span>
<br>
<span class="auto-type code">s</span>
<br>
<div class="auto-type">s</div>
<span class="auto-type code">heooelmmrleeeo !toietsxh tear bteetxtter text</span>
关注最后一行,您可以看到每条消息都已键入到最后一个元素中。通过用数字替换原始消息中的每个字符,可以使这一点更加明显:
<span class="auto-type code">111111</span>
<br />
<span class="auto-type code">222222222</span>
<br />
<div class="auto-type">333333333333333</div>
<span class="auto-type code">444444444444444444444</span>
呈现:
<span class="auto-type code">1</span>
<br>
<span class="auto-type code">2</span>
<br>
<div class="auto-type">3</div>
<span class="auto-type code">412341234123412341234234234234343434343434444444</span>
所以最后一行是这样构建的:
heooelmmrleeeo !toietsxh tear bteetxtter text
│││└ the 'e' from "here" in the fourth element
││└ the 'o' from "some" in the third element
│└ the 'o' from "some" in the second element
└ the 'e' from "hello" in the first element
发生这种情况的原因是因为这个 for-loop,其中变量 element
的范围不是 for-loop,这意味着它被每个 typing
共享函数,并将引用 elements
:
for (element of elements) { // <- this element variable is a global!
/* ... */
}
您可以通过简单地用 let
或 const
重新定义 element
变量来解决您的主要问题:
for (const element of elements) { // <- this element variable is now scoped to this for-loop
/* ... */
}
这个改动的效果可以看下面StackSnippet:
window.onload = () => {
let elements = document.getElementsByClassName("auto-type");
for (const element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
let charIndex = 0;
const typing = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typing, 100);
charIndex++;
}
};
typing();
}
};
body {
background-color: black;
color: lime;
font-family: consolas, monospace;
}
<span class="auto-type code">hello!</span>
<br />
<span class="auto-type code">some text</span>
<br />
<div class="auto-type">some other text</div>
<span class="auto-type code">here is a better text</span>
其他问题
由于 JavaScript 的性质,加载页面和所有依赖项可能需要一段时间。这意味着您的消息在页面加载时对用户可见,这可能不是您想要的。所以你应该用 CSS 隐藏消息,然后在你准备好时显示它们。
.auto-type {
visibility: hidden; /* like display:none; but still consumes space on the screen */
}
然后当您清空文本时,再次使元素可见:
const text = element.innerHTML.toString();
element.innerHTML = "";
element.style.visibility = "visible";
更正代码
您需要修改“输入”功能,以便在输入上一条消息之前不触发。我们可以通过将每个“键入”函数更新为 return 一个 Promise,然后将这些 Promise 链接在一起来做到这一点。
/**
* Types text into the given element, returning a Promise
* that resolves when all the text has been typed out.
*/
function typeTextInto(element, text, typeDelayMs = 100) {
return new Promise(resolve => {
let charIndex = 0;
const typeNextChar = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typeNextChar, typeDelayMs);
charIndex++;
} else {
resolve(); // finished typing
}
};
typeNextChar(); // start typing
});
}
window.onload = () => {
const elements = document.getElementsByClassName("auto-type");
const elementTextPairs = [];
// pass 1: empty out the contents
for (element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
element.style.visibility = "visible";
elementTextPairs.push({ element, text });
}
// pass 2: type text into each element
let chain = Promise.resolve(); // <- empty Promise to start the chain
for (elementTextPair of elementTextPairs) {
const { element, text } = elementTextPair; // unwrap pair
chain = chain // <- add onto the chain
.then(() => typeTextInto(element, text));
}
// basic error handling
chain.catch(err => console.error("failed to type all messages:", err));
};
这可以使用 async
/await
进一步清理,并将输入逻辑拆分到它自己的函数中。这显示在下面的工作 StackSnippet 中:
/**
* Types text into the given element, returning a Promise
* that resolves when all the text has been typed out.
*/
function typeTextInto(element, text, typeDelayMs = 100) {
return new Promise(resolve => {
let charIndex = 0;
const typeNextChar = () => {
element.innerHTML += text.charAt(charIndex);
if (charIndex < text.length) {
setTimeout(typeNextChar, typeDelayMs);
charIndex++;
} else {
resolve(); // finished typing
}
};
typeNextChar(); // start typing
});
}
window.onload = async () => {
try {
const elements = document.getElementsByClassName("auto-type");
const elementTextPairs = [];
// pass 1: empty out the contents
for (element of elements) {
const text = element.innerHTML.toString();
element.innerHTML = "";
element.style.visibility = "visible";
elementTextPairs.push({ element, text });
}
// pass 2: type text into each element
for (elementTextPair of elementTextPairs) {
const { element, text } = elementTextPair; // unwrap pair
await typeTextInto(element, text); // await in a for-loop makes the asynchronous work run one-after-another
}
} catch (err) {
// basic error handling
console.error("failed to type all messages:", err);
}
};
body {
background-color: black;
color: lime;
font-family: consolas, monospace;
}
.auto-type {
visibility: hidden;
}
<span class="auto-type code">hello!</span>
<br />
<span class="auto-type code">some text</span>
<br />
<div class="auto-type">some other text</div>
<span class="auto-type code">here is a better text</span>