Javascript typewriter snippet makes browser not responding

Viewed 51

I had an approach to add a typewriter effect to my text using Javascript. I used the following snippet to do so :

const text = document.querySelector('.type')

const content = text.textContent
let counter = 0
text.textContent = ''
while (counter < content.length) {
    setTimeout(function () {
        text.textContent += content[counter];
        counter += 1
    }, 1000)
}

I know that this approach seems ridiculous , but I'm curious why it does this. When I try to run this snippet, the browser (Chrome) becomes not responding. I want to know if this code generates an infinite loop, and why? And if someone can provide me some alternative way to get the wanted result.

3 Answers

Because the counter in the setTimeout isn't the same as the counter on the outside. If you do a console log on counter after your while, it'll still be 0.

That has to do with scopes, which is a bit too much to explain here, but that is what you're looking for. The easy explanation is that inside a function(){}, now being applied in the setTimeout(), the variables outside that function do not exist.

Because it isn't the same variable, the one on the outside never gets updated, so your loop never stops adding more timeouts, so your browser never stops.


Apart from that, in your current code, all characters will appear at the same time, after 1000ms. The setTimeout doesnt make the while "wait" 1000ms, it just tell javascript "do this taks, after 1000" and then continues. You create something like a queue, telling them all to do that task after 1000ms.

A solution to both issues (but do read up on scopes!), is to use this knowledge to your advantage:

while (counter < content.length) {
    setTimeout(function () {
        text.textContent += content[counter];
    }, 1000 * counter++)
}

Now we all put them into that 'queue' again, but this time we tell every next one to wait 1000ms more.

setTimeout is not 'sleep for 1sec then do the code then continue'.

What setTimeout does it adds it's code to a 'heap' and continues to run to the next line. After timeout period (1000ms), code from heap is executed.

So, your function keeps adding code to the heap, for a 1000ms, until first timeout is fired (it will not fire, because you are keep adding new timeouts - the process is stuck), when it supposed to increment the counter.

Thanks to @Martijn & @Alex Brohshtut for helping me recognize the problem and offering helpful solutions.

I did maintain my code using their notices and by looking up through the internet and finally I did it in that way:

const text = document.querySelector('.type')

const content = text.textContent
let counter = 0
text.textContent = ''

while (counter < content.length) {
    setTimeout((function (counterCopy) {
        return function () {
            text.textContent += content[counterCopy];
        }
    }(counter)), 1000 * counter++)
}

And here are the problems with my first code:

  1. I modified the value of the variable counter only inside the callback function of setTimeout, and its value didn't change inside the loop due to scope reasons. So it was an infinite loop which made my browser not responding for a night.

    solution: I had to change the value of counter outside the callback function

  2. I misunderstood the setTimeout function, thought it would make script stop for a time and then continue. The two friends showed me that it only schedules the callback function inside it to be executed after a specific time. So, I had to increase that time in every iteration.

    solution: I took @Martijn's advice and used 1000 * counter++ as a time for it.

  3. When I make the function inside setTimeout get executed after 1000 ms, the loop continues and increases the value of counter. so when the function gets executed, the counter now has its highest value which is equal to content.length.

    solution: I used a function call which returns the callback function itself, but this time with the counter as an argument to it. This looked for me a bit confusing but it worked fine at the end.

    setTimeout((function(counterCopy){
        return function() {
            console.log(counterCopy)
        }
    }(counter)), 1000 * counter++)
    

Another Approach which I saw in A YouTube Tutorial HERE

Using setInterval instead of setTimeout:

const text = document.querySelector('.type')

const content = text.textContent
let counter = 0
text.textContent = ''

var typewriter = setInterval(function(){
    text.textContent += content[counter];
    counter++;
    if (counter > (content.length - 1)) {
        clearInterval(typewriter);
    }
}, 1000);
Related