0

Good day ! I'm learning javascript and have a problem with the timers. I just wanted to check if the onload event is triggered once the whole page is written including the text modified via javascipt. For that purpose I wanted to slow down the writing of the text by inducing a 200 ms delay between characters.

The test I use is the following:

<!DOCTYPE html>    
<head>
<meta charset="utf-8">
<title>Onload test</title> 
<script>

    function load() {
        alert("The page is considered loaded !");
    }

    function writeSlowly(text, timer) {
        var L= text.length;
        var st = "";
        function write (seq) {
            document.getElementById("st").innerHTML = seq;
        };
        for (var i = 0; i < L; i += 1) {
            st += text[i];
            setTimeout(write(st), timer);
        };
    };

</script>
</head>
<body>
<p id="st"></p>
<script>
    writeSlowly("This pages takes a while to load", 200);
    window.onload = load;
</script>
</body>
</html>

The page loads as if there were no delay at all. Actually I expected the text (32 characters long) to take about 32 x 200 ms =~ 7 seconds. When debugging (with Firebug - I use Firefox 30) the program steps through the lines but the timer has no effect. The page displays almost instantaneously.

RAH
  • 11
  • 2
  • 1
    possible duplicate of [How do I add a delay in a JavaScript loop?](http://stackoverflow.com/questions/3583724/how-do-i-add-a-delay-in-a-javascript-loop) – Adam Jul 08 '14 at 18:12
  • Thank you Adam, Cheruvian & al. I will practice further. – RAH Jul 08 '14 at 18:22
  • Thank you Cheruvian and Adam for taking the time to write a correct solution. Both your scripts are working. I will make sure I understand the gist of it. – RAH Jul 08 '14 at 22:14
  • Back to my original question : when is the page considered loaded ? The answer is: very early. It doesn't wait for the page to finish executing the script. One can also witness this on Firebug, in the HTML tab: one can see the paragraph expanding over time:

    This page ... until it is complete. So I think to be correct in saying: "the page is considered loaded when the static elements are read and the code for the dynamic elements are read but not necessarily fully executed."

    – RAH Jul 08 '14 at 22:23
  • When all the static content is loaded, including CSS files, images, and the HTML DOM, the `onload` function will run. The page can't wait for the `dynamic` elements to load, since they are done using a script, and it is not possible to determine when they are 'done' loading. The setTimeout method is asynchronous, meaning the rest of the code doesn't wait for it to run, and will continue without it, which is why `window.onload` runs before the string is output. – Adam Jul 09 '14 at 17:33
  • From your question, it sounded like you wanted to trigger something when the new text is added. I added a new `done` function to my code below to give you an example of how to do that. – Adam Jul 09 '14 at 18:48
  • Thank you Adam for your comment. I'm fully aligned with you on the `onload` event. I did further analyze the various codes making sure I understood their working and performance. I have examined 3 variations and will make my comments soon: As per Cheruvian. [link](http://jsfiddle.net/rheymans/kf5rK/); as per Adam [link](http://jsfiddle.net/rheymans/Qe39b/); as per RAH [link](http://jsfiddle.net/rheymans/LZt2Q/). – RAH Jul 10 '14 at 10:44
  • The 3 variations display each the same paragraph (2880 characters long) inducing a delay of 2ms for each character and times the duration of the script. Adam and RAH are quite similar (about 5760 ms total) while Cheruvian lasts in the 16000 ms range. The main difference comes from the fact that Cheruvian calls the writeNext function every 2 ms endlessly (one must clearInterval after the whole text has been written). In fact this algorithm uses 2880 x 2 ms = 5760 ms just in elapsed time without counting execution time for the function. Adam and RAH use setTimeout. – RAH Jul 10 '14 at 10:58
  • Lastly, there is a similarity and a difference between Adam and RAH. The similarity is the loop that starts - within less than a few milliseconds (?) - 2880 timers of duration T, 2T, 3T, ... 2779T, 2880T. Each timer will callback the writer function upon the elapsing of their respective duration. The difference is that Adam invokes the callback function with a parameter (not documented syntax ?) while RAH uses the callback reference only together with a function property that updates itself each time the callback function is called back. – RAH Jul 10 '14 at 11:38
  • I wish to mention two references that were useful to me in better understanding the JS behavior in my piece of code: **1) The Secrets of the JavaScript Ninja by John Resig and Bear Bibeault** (Memorizing previously computed values - Listing 4.9 p.74) and **2) JavaScript: The Good parts by Douglas Crockford** (Bad example and its explanation p. 39). The listing 4.9 of the Ninja shows that functions are objects that can have properties. While Adam and RAH samples are equally fast, one might argue that the RAH code imitating the Ninja has a certain elegance. – RAH Jul 10 '14 at 12:01
  • On my above comment on the loop running within a few seconds, I found the following order of magnitude: a simple loop adding numbers from 1 to 1 million and displaying the result on a page runs in about 1.5 to 1.6 second, that's 1,000 iterations within 1.6 msec. Hence very short compared to timeouts of 2, 5, 10, 200 msec. – RAH Jul 10 '14 at 13:55
  • Regarding the callback function with a parameter, it's called a [closure](http://stackoverflow.com/questions/111102/how-do-javascript-closures-work), and it is a way of passing a parameter to a callback, that does not natively include that parameter. See how the `writer` function actually returns another function? That allows the parameter `toWrite` to be in scope without having to make it global, unlike Cheruvian's solution. – Adam Jul 10 '14 at 20:44

3 Answers3

0

You are creating separate timers for each letter, all start at time 0 and all are executing at time 200ms.

Further, the function for setTimeout needs to be a callback (the function will be called back into when the timer expires). You are passing it a null. write() does not return anything much less a function.

So you are actually writing each letter every time you hit the loop, resulting in no delay

To achieve what you are trying I would do something along the lines of...

var str;
var index = 0;

function writeSlowly(text, timer) {
    str = text;
    setInterval(writeNext, timer);
};
function writeNext()
{
    if(index < str.length - 1)
         document.getElementById("st").innerHTML = str.substring(0, ++index);
    else 
         document.getElementById("st").innerHTML = str;
}
Cheruvian
  • 5,628
  • 1
  • 24
  • 34
0

I made a few modifications and made it work, you can try it out at this link.

One issue is that you were calling the write function, not setting it as a callback. Another issue is that the string you wanted to write was getting filled up completely before you wrote it. Finally, the timer was being set at 200ms from the current time for all writes, instead of introducing a delay of 200ms for each character written.

The updated Javascript is below.

function writeSlowly(text, timer) {
    var L= text.length;
    var st = "";
    var delay = 0;
    for (var i = 0; i < L; i += 1) {
        st += text[i];
        delay += timer
        setTimeout(writer(st), delay);
    };
}

function writer(toWrite) {
    return function() {
        document.getElementById("st").innerHTML = toWrite;
    }
}

Edit:
I updated the JSFiddle.

When the text is done scrolling, it it will trigger the done() function and run whatever code you'd like to run at that point.

Adam
  • 4,445
  • 1
  • 31
  • 49
0

Following the answers given, their analysis (see comments below the original question) my preferred answer is the following - which like the one by Adam - takes almost exactly 5760 msec for a 2880 characters string with a 2 msec delay per character. The central part is below and the full answer on JS Fiddle.

function writeSlowly(text, timer) {
    var L= text.length;
    var delay = 0;
    for (var i = 0; i < L; i += 1) {
        setTimeout(writer, delay += timer);
    };
    function writer() {
        if (!writer.seq) writer.seq = 0;  // Create a function property that increments on each call.
        document.getElementById("slowpara").innerHTML = text.substring(0, ++writer.seq);
    }
}

I thank StackOverflow, the community, in particular Cheruvian and Adam for their generous help.

RAH
  • 11
  • 2