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>

8
  • 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 </body> tag
    – mplungjan
    Commented Nov 16, 2014 at 6:44
  • the fiddle is precisely an example of what you describe and it clearly doesn't work. Commented Nov 16, 2014 at 6:47
  • @MatthewJamesDavis How is your unrendered content rendered?
    – Dave Chen
    Commented Nov 16, 2014 at 6:49
  • Arbitrarily. Potentially through knockout, or appending document fragments through the DOM API. Commented Nov 16, 2014 at 6:50
  • 2
    Note: the setTimeout(function(){}, 0) method does not appear to work in FireFox. It requires a timeout of ~50-100ms.
    – JstnPwll
    Commented Nov 18, 2014 at 15:32

4 Answers 4

13
+100

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.

7
  • any idea why google suggests you suggests you inject it instead of include it? Commented Nov 18, 2014 at 18:26
  • @MatthewJamesDavis Two reasons that I know of. Only one seems relevant here: Some browsers don't support the async attribute.
    – svidgen
    Commented Nov 18, 2014 at 18:28
  • @MatthewJamesDavis See browser support for async here: developer.mozilla.org/en-US/docs/Web/HTML/Element/…
    – svidgen
    Commented Nov 18, 2014 at 18:29
  • i would include async in your answer for future seekers Commented Nov 22, 2014 at 21:09
  • @svidgen: Instead of script injection can i directly use the script tag in the header section with aync attribute ? (like below) <script async src="/path/to/script.js" type="text/javascript"></script>
    – Soundar
    Commented Jun 6, 2015 at 13:37
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();
}
1
  • 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" Commented Nov 18, 2014 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);
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>
1
  • 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. Commented Nov 16, 2014 at 6:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.