HTML/JS: How to prevent a render before script execution?

121 Views Asked by At

I've just noticed strange behaviour with rendering before script is being performed. I always thought, that there is no chance for browsers to dequeue a task from render queue and execute it before script tag, since this is a single thread. And as you will see, it's not true. There is a simple example of code below:

console.log('SCRIPT EXECUTED')
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>HTML</title>

    <script>
        requestAnimationFrame(function animate(time) {
            console.log('RAF', performance.now());
        });
    </script>
</head>

<body>
<h1>Hello world</h1>

<script src="./script.js"></script>
</body>
</html>

One may see inconsistent output in console. Sometimes render comes up before script and vice versa. It may cause to flickering in UI for example.

Could anyone give a meaningful explanations of this?

3

There are 3 best solutions below

8
Kaiido On BEST ANSWER

External classic scripts are parser-blocking, i.e. the HTML parser will wait until the script's execution before continuing parsing the remaining of the document.

<script src="data:text/javascript,console.log(document.body.children[document.body.children.length - 1].nodeName);"></script>
<span>Should log SCRIPT and not SPAN</span>

But to also be render-blocking, they need to be in the <head> of the document.

/*
 * Since StackSnippets do wrap the HTML & JS content in the <body>,
 * we use another <iframe> for our demo.
 * But StackSnippet's console isn't available there,
 * so we use postMessage and log from here.
 */
onmessage = ({data}) => console.log(...data);
<iframe srcdoc="
<!DOCTYPE HTML>
<html>
  <head>
    <script>
      requestAnimationFrame(function animate(time) {
        parent.postMessage(['RAF', performance.now()], '*');
      });
    </script>
    <script src='./script'></script>
    <script>
      parent.postMessage(['SCRIPT EXECUTED'], '*')
    </script>
  </head>
  <body>
    <h1>HELLO WORLD</h1>
  </body>
</html>"></iframe>

Note that Firefox apparently doesn't support render-blocking yet.

Also, you may be interested in the blocking="render" attribute, which allows a <script> to not be parser blocking (defer, async, or module) while being render-blocking, though this is currently only available for external scripts, but this might change in the near future.

onmessage = ({data}) => console.log(...data);
/* In Chrome this logs
 * SCRIPT EXECUTED
 * H1
 * "RAF", a number
 */
<iframe srcdoc="
<!DOCTYPE HTML>
<html>
  <head>
    <script>
      requestAnimationFrame(function animate(time) {
        parent.postMessage(['RAF', performance.now()], '*');
      });
    </script>
    <script defer src='data:text/javascript,parent.postMessage([document.body.children[document.body.children.length - 1].nodeName], `*`);'></script>
    <script>
      parent.postMessage(['SCRIPT EXECUTED'], '*')
    </script>
  <head>
  <body>
    <h1>HELLO WORLD</h1>
  </body>
</html>"></iframe>

3
TP95 On

It doesn't matter whether it's an external script or an inline script - they are executed in the order they are encountered in the page.

The biggest culprit for the inconsistency in output is the method requestAnimationFrame(). As per its documentation even if this script runs before script.js all the time, the output timing will depend on a lot of other factors like:

  • Delay of time takes your browser to respond to your request
  • Your display refresh rate
  • Other browser-specific parameter that are used to improve performance and battery life.

So that means even if your scripts are being run in the correct order under the hood, the rendering timing will be off depending on how fast the browser reacts to your requestAnimationFrame().

0
Suramuthu R On

This is the default arrangement to provide good user experience. HTML won't wait till the javascript is executed. Even if it is a small slideshow which involves javascript, the content is more important to the user than the slideshow. Also if there is any user interaction which involves javascript, the user is going to interact only after the page is loaded. if at all you want the HTML to wait till javascript is loaded, you can use script defer tag for your javascript. It is as follows:

<body>

<!-- HTML content-->

<script defer>
    
    document.addEventListener("DOMContentLoaded", function() {
        // Code to be executed after HTML is loaded
       
    });
</script>
</body>

Please remember that the delay in HTML page loading could affect the SEO factors.