8

I have an html page with some pre-rendered content and some yet un-rendered content. I want to display the pre-rendered content immediately, and then begin rendering the rest of the content. I am not using jQuery.

See the following snippet. I have tried this various ways, including injecting my script before the closing body tag and providing my script to populate the DOM as a callback to window.onload, document.body.onload, and document.addEventListener('DOMContentLoaded'). In every case, the page does not display the pre-rendered content until the rest of the content is rendered.

<html><head></head>
  <body>
    <header>What it is, my doge?</header>
    <div id="main"></div>
    <script>
      var main = document.getElementById('main');
      for (var i = 0; i < 500; i++)
        main.innerText += new Date();
    </script>
  </body>
</html>

<html><head></head>
  <body>
    <header>What it is, my doge?</header>
    <div id="main"></div>
    <script>
      var main = document.getElementById('main');
      document.body.onload = function() {
        for (var i = 0; i < 500; i++)
          main.innerText += new Date();
      };
    </script>
  </body>
</html>

<html><head></head>
      <body>
        <header>What it is, my doge?</header>
        <div id="main"></div>
        <script>
          var main = document.getElementById('main');
          window.onload = function() {
            for (var i = 0; i < 500; i++)
              main.innerText += new Date();
          };
        </script>
      </body>
    </html>

<html><head></head>
      <body>
        <header>What it is, my doge?</header>
        <div id="main"></div>
        <script>
          var main = document.getElementById('main');
          document.addEventListener('DOMContentLoaded', function() {
            for (var i = 0; i < 500; i++)
              main.innerText += new Date();
          });
        </script>
      </body>
    </html>

One case that has worked is window.setTimeout with 0 timeout. However, this simply defers the function until there is nothing left to do. Is this the best practice, here?

<html><head></head>
<body>
    <header>What it is, my doge?</header>
    <div id="main"></div>
    <script>
      var main = document.getElementById('main');
      window.setTimeout(function() {
        for (var i = 0; i < 500; i++)
          main.innerText += new Date();
      }, 0);
    </script>
</body>
</html>
Matthew James Davis
  • 12,134
  • 7
  • 61
  • 90
  • 1
    1) we need code here. 2) your description does not match the fiddle. 3) window.onload will trigger after all content is loaded. All you need to do is to put your call before the `

    ` tag

    – mplungjan Nov 16 '14 at 06:44
  • the fiddle is precisely an example of what you describe and it clearly doesn't work. – Matthew James Davis Nov 16 '14 at 06:47
  • @MatthewJamesDavis How is your unrendered content rendered? – Dave Chen Nov 16 '14 at 06:49
  • Arbitrarily. Potentially through knockout, or appending document fragments through the DOM API. – Matthew James Davis Nov 16 '14 at 06:50
  • Can you add your code with setTimeout here? – Sampath Liyanage Nov 16 '14 at 07:15
  • 2
    Note: the `setTimeout(function(){}, 0)` method does not appear to work in FireFox. It requires a timeout of ~50-100ms. – JstnPwll Nov 18 '14 at 15:32
  • Check this post: http://stackoverflow.com/questions/1795089/how-can-i-detect-dom-ready-and-add-a-class-without-jquery – SmartDev Nov 18 '14 at 15:41
  • [according to Mozilla](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers.setTimeout#Minimum_delay_and_timeout_nesting) `setTimeout(function(){},0)` sets it (or it should anyway, as defined by the HTML5 spec) to a minimum of 4ms. Any value when less than 4ms is or should be set to 4ms. – Arthur Weborg Nov 22 '14 at 22:33

4 Answers4

13

In terms of a best practice, there isn't one. In terms of a good, common, and acceptable practices, there are a handful. You've hit one:

setTimeout(function() { }, 1);

In this case, the function is executed within the browser's minimum timeout period after all other in-line processing ends.

Similarly, if you want to ensure your function runs shortly after some condition is true, use an interval:

var readyCheck = setInterval(function() {
  if (readyCondition) {
    /* do stuff */
    clearInterval(readyCheck);
  }
}, 1);

I've been using a similar, but more generalized solution in my own work. I define a helper function in the header:

var upon = function(test, fn) {
    if (typeof(test) == 'function' && test()) {
        fn();
    } else if (typeof(test) == 'string' && window[test]) {
        fn();
    } else {
        setTimeout(function() { upon(test, fn); }, 50);
    }
}; // upon()

... and I trigger other functionality when dependencies are resolved:

upon(function() { return MyNS.Thingy; }, function() {
  //  stuff that depends on MyNS.Thingy
});

upon(function() { return document.readyState == 'complete';}, function() {
  // stuff that depends on a fully rendered document
});

Or, if you want a more authoritative good practice, follow Google's example. Create an external async script and inject it before your first header script:

var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true;
s.src = '/path/to/script.js';
var header_scripts = document.getElementsByTagName('script')[0];
header_scripts.parentNode.insertBefore(s, header_scripts);

Google's solution theoretically works on all browsers (IE < 10?) to get an external script executing as soon as possible without interfering with document loading.

If you want an authoritative common practice, check the source for jQuery's onready solution.

Community
  • 1
  • 1
svidgen
  • 13,744
  • 4
  • 33
  • 58
  • any idea why google suggests you suggests you inject it instead of include it? – Matthew James Davis Nov 18 '14 at 18:26
  • @MatthewJamesDavis [Two reasons](http://stackoverflow.com/questions/14815481/why-does-google-analytics-dynamically-inject-javascript-into-the-page) that I know of. Only one seems relevant here: Some browsers don't support the `async` attribute. – svidgen Nov 18 '14 at 18:28
  • @MatthewJamesDavis See browser support for `async` here: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#Browser_compatibility – svidgen Nov 18 '14 at 18:29
  • i would include async in your answer for future seekers – Matthew James Davis Nov 22 '14 at 21:09
  • @svidgen: Instead of script injection can i directly use the script tag in the header section with aync attribute ? (like below) `` – Soundar Jun 06 '15 at 13:37
  • @svidgen sorry about disturb. in the setInterval solution, does the clearInterval expression should in the if block? Or your stuff may not invoke. – zlx_star May 19 '16 at 09:04
  • Yes.. good catch! I'll correct that when I'm not on my phone. – svidgen May 19 '16 at 12:23
4

Depending on your browser requirements you can use the async tag and import your script after content loads. This probably accomplishes the same thing as setTimeout(func, 0), but perhaps it's a little less hacky.

See http://plnkr.co/edit/7DlNWNHnyX5s6UE8AFiU?p=preview

html:

...
<body>
  <h1 id="main">Hello Plunker!</h1>
  <script async src="script.js"></script>
</body>
...

script.js:

for(var i=0; i<500; ++i) {
  document.getElementById('main').innerText += new Date();
}
Clint Powell
  • 2,368
  • 2
  • 16
  • 19
  • its a lot less hacky. the setTimeout method says "hey, stop everything while you download this script, then finish doing whatever you would be doing, then execute the script" where async says "finish doing all the things, then execute the script whenever its ready" – Matthew James Davis Nov 18 '14 at 16:49
2

I've used this to effect before:

var everythingLoaded = setInterval(function() {
  if (/loaded|complete/.test(document.readyState)) {
    clearInterval(everythingLoaded);
    init(); // this is the function that gets called when everything is loaded
  }
}, 10);
Nick Salloum
  • 2,158
  • 12
  • 13
0

I think what you want to do is use an onload event on the tag. This way first the "What it is, my doge?" message will appear while the javascript is processed.

I also set a timeout inside the loop, so you can see better the lines being added.

<html>
    <head>
        <script>
            myFunction = function() {
                for (var i = 1000; i > 0; i--) {
                    setTimeout(function() {
                        main.innerText += new Date();
                    }, 100);
                }
            };
        </script>
    </head>

    <body onload="myFunction()">
        <header>What it is, my doge?</header>
        <div id="main"></div>
    </body>
</html>
Pedro Moreira
  • 965
  • 5
  • 8
  • Appreciate the answer. However, this is not as optimal as the idea I posted, setting one call to window.setTimeout outside of the callback with a timeout of 0, which is much faster. – Matthew James Davis Nov 16 '14 at 06:59