Samuel E.
Samuel E.

Reputation: 2422

setTimeout within a for loop not executing the function passed to it

i'm trying to make a "console typing effect" with js, and in the next function i take the text of an element, then i use a "for" loop for slicing that text and paste in within with a delay.

After debugging the code in chrome i can see that javascript doesn't run the setTimeout... it just ignores it.

function type() {
    var text = document.querySelector('.console-effect').textContent;
    for (var i = 0; i <= text.length; i++) {
        setTimeout(function() {
            document.querySelector('.console-effect').textContent = text.substr(0, i)
        }, 50);
    }
}
type();

Upvotes: 0

Views: 121

Answers (7)

Nate
Nate

Reputation: 1276

I didn't want to believe @blex, but he was correct about the scope. He was right on both points he made, but the closure stunned me. How have I never encountered this and been forced to puzzle my way out before?

So the idea here is that instead of scheduling a dozen or so calls to your function at the beginning, you only schedule the next call. After that one is called, it sees if it needs to schedule the next.

function type() {
    var text = document.querySelector('.console-effect').textContent;
    var i = 0;
    var typeNext = function() {
        ++i;
        document.querySelector('.console-effect').textContent = text.substr(0, i);
        if(i < text.length) {
            setTimeout(typeNext, 50);
        }
    }
    setTimeout(typeNext, 50);
}
type();
<span class="console-effect">This is a test</span>

Upvotes: 1

alfredopacino
alfredopacino

Reputation: 3241

a solution without 50*i, take a look at the css effect too. You problem is setTimeout is executed asynchronously (the 'control flow' doesn't wait for those 50ms), so they are fired all together with value i = text.length (if your text is small enough)

<p class="console-effect">console effects</p>
<script>
function type() {
        var i=0;
        var t = document.querySelector('.console-effect').textContent;
        var fn = setInterval(function() {
            print_text(++i,t,fn)
        }, 500);
}
function print_text(i,t,fn){
    if(i <= t.length){
                document.querySelector('.console-effect').textContent = t.substr(0, i)
    } else clearInterval(fn)
}
type();

</script>
<style>
@-webkit-keyframes blinker {  
  from { opacity: 1.0; }
  to { opacity: 0.0; }
}
.console-effect:after{
    content:'_';
    text-decoration: blink;
    -webkit-animation-name: blinker;
    -webkit-animation-duration: 0.2s;
    -webkit-animation-iteration-count:infinite;
    -webkit-animation-timing-function:ease-in-out;
    -webkit-animation-direction: alternate;
    }
</style>

Upvotes: 0

Alex Kudryashev
Alex Kudryashev

Reputation: 9460

Do something like this.

function type() {
    var text = document.querySelector('.console-effect').textContent;
    document.querySelector('.console-effect').textContent = '';//clear content
    for (var i = 0; i <= text.length; i++) {
        setTimeout(function(j) {
            document.querySelector('.console-effect').textContent += text[j];// or .charAt(j)
        }, 50 * i, i);
        // 50 * i sets timeout for each iteration
        // i (3rd arg) passes to the inner function
    }
}
type();

Better version.

function type(text) {
    var textObj = document.querySelector('.console-effect');
    for (var i = 0; i <= text.length; i++) {
        setTimeout(function(ch) {
            textObj.textContent += ch;
        }, 50 * i, text[i]);
        // 50 * i sets timeout for each iteration
        // i (3rd arg) passes to the inner function
    }
}
type('.console-effect');

Upvotes: 0

Vasil Nedyalkov
Vasil Nedyalkov

Reputation: 65

var text = document.querySelector('.console-effect').textContent;
var index = 0;
setInterval(function(){
     document.querySelector('.console-effect').textContent = text.substr(0, index);
     if(index == text.lenght){
         clearInterval(this);
     }
index++;
},1000);

Upvotes: 0

blex
blex

Reputation: 25634

Your setTimeouts are all executing at the same time, because the for loop does not wait for them to execute on each iteration. You have to delay each timeout using a value such as 50*i.

Then, to preserve the value of i in this case, you'll need to use a closure. Otherwise, by the time your timeouts come to an end, the loop will be over, and i will be the final value, for all of them.

var text = document.querySelector('.console-effect').textContent;

for (var i = 0; i <= text.length; i++) {
  (function(i) {  
    setTimeout(function() {
      document.querySelector('.console-effect').textContent = text.substr(0, i);
    }, 50*i);
  })(i);
}
body{background: #333;}
.console-effect{color: #0f0; font-family: monospace; font-size: 2em;}
<div class="console-effect">This is some example text</div>

Upvotes: 3

Vasil Nedyalkov
Vasil Nedyalkov

Reputation: 65

setTimeout run only once(in your case after delay of 50ms).

For this purpose you should use setInterval

Upvotes: -2

melchor629
melchor629

Reputation: 342

Is not a good idea to make functions inside a loop in Javascript, I had bad experiences with it.

This code done this way should work correctly:

function type() {
    var text = document.querySelector('.console-effect').textContent;
    var loopFunc = function(i) {
        return function() {
            document.querySelector('.console-effect').textContent = text.substr(0, i)
        };
    };
    for (var i = 0; i <= text.length; i++) {
        setTimeout(loopFunc(i), 50);
    }
}
type();

Upvotes: 1

Related Questions