Reputation: 15
I'm trying to make a typing animation but it doesn't work and I don't know why. I know I could do it in CSS but I'd like to try it in JS. Problem is probably with the function itself I'm not the best in JS.
<p class="typing-animation">this will be animated</p>
<p class="typing-animation">this aswell</p>
<script>
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const toType = document.getElementsByClassName("typing-animation");
document.getElementsByClassName("typing-animation").textContent = "";
(async (toType) => {
for (let each of toType) {
text = each.textContent;
each.textContent = "";
let i = 0;
for (let every of text) {
every.textContent += text[i];
await sleep(200);
i++;
}
}
})(toType);
</script>
Upvotes: 1
Views: 1356
Reputation: 4975
You are on the right path. I've just cleaned things up a bit for readability.
Here is a codesandbox of it: https://codesandbox.io/s/typing-animation-kdn8v
I've made the assumption that you want the animations to happen in parallel. For this reason, we want to run through each HTML element and call our animateType
function on each of them without any awaiting.
Each call of animateType
kind of means there is a copy of this function running for this HTML element. This way, the variables like text
, inside of the function don't get mixed up across HTML elements. This is called a "closure". All the variables inside of that "instance" of the function, live only inside of that instance of the function.
We store the original text, then clear the HTML element's contents, then just like you did we loop the letters of the cached text, and add each one to the HTML element.
// Stackoverflow: https://stackoverflow.com/questions/70573442/typing-animation-on-each-element-with-its-class
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const clearAndCache = (elements) => {
let cachedText = [];
for (let element of elements) {
cachedText.push(element.textContent);
element.textContent = "";
}
return cachedText;
};
const animateTypeSync = async (typeables, ms) => {
const cachedText = clearAndCache(typeables);
for (let i = 0; i < typeables.length; i++) {
for (let character of cachedText[i]) {
typeables[i].textContent += character;
await sleep(ms);
}
}
};
const animateTypeAsync = (typeables, ms) => {
const cachedText = clearAndCache(typeables);
Array.from(typeables).forEach(async (element, i) => {
for (let character of cachedText[i]) {
element.textContent += character;
await sleep(ms);
}
});
};
const elementsSync = document.getElementsByClassName("typing-animation-sync");
animateTypeSync(elementsSync, 200);
const elementsAsync = document.getElementsByClassName("typing-animation");
animateTypeAsync(elementsAsync, 150);
<!DOCTYPE html>
<html>
<head>
<title>Typing Animation</title>
<meta charset="UTF-8" />
</head>
<body>
<h1>Typing Animations</h1>
<div style="width: 100%; display: inline-flex;">
<div style="width: 50%;">
<h2>Async</h2>
<p class="typing-animation">These will run</p>
<p class="typing-animation">at the same time</p>
</div>
<div style="width: 50%;">
<h2>Sync</h2>
<p class="typing-animation-sync">These will animate</p>
<p class="typing-animation-sync">one at a time</p>
</div>
</div>
</body>
<script src="src/index.js"></script>
</html>
Edit: Added sync version to code after comment from OP.
Upvotes: 1
Reputation: 1326
I think this is what you're after?
<p class="typing-animation">this will be animated</p>
<p class="typing-animation">this aswell</p>
<script>
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
// This is an array of html elements, since getElementsByClassName returns an array, since there can be more than one html element with that class.
const elements = document.getElementsByClassName("typing-animation");
(async (typedElements) => {
let texts = []
for (let element of typedElements){
// The property to access the text inside the elements .innerHTML, if its a user interactuable element such as <input> or <textarea> then its .value
texts.push(element.innerHTML);
element.innerHTML = "";
}
// texts and typedElements has the same length.
for (let i = 0; i < texts.length; i++) {
for (let character of texts[i]) {
typedElements[i].innerHTML += character;
await sleep(200)
}
}
})(elements);
</script>
If you want that effect on several places, and easier to implement, there's a library that already does that, easier to customize and use: Typed.js
<span class="typed"></span>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script>
// For more examples check: https://github.com/mattboldt/typed.js
// Full docs at: https://mattboldt.github.io/typed.js/docs/
const options = {
strings: ['This will be typed!', 'This too! ^500 <br> And this will go under!'],
typeSpeed: 40,
backSpeed: 40,
backDelay: 2000,
};
// We'll bind the typing animation to the .typed class.
let typed = new Typed('.typed', options);
</script>
Upvotes: 1