DOMContentLoaded

This event occurs when the browser fully loaded HTML, and the DOM tree is built, but external resources like pictures <img> and stylesheets may be not yet loaded.

The DOMContentLoaded event happens on the document object. So you cannot bind it with any element. We must use addEventListener to catch it:

<script>
  function ready() {
    alert('DOM is ready');

    // image is not yet loaded (unless was cached), so the size is 0x0
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  }

  document.addEventListener("DOMContentLoaded", ready);
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

In the example the DOMContentLoaded handler runs when the document is loaded and does not wait for the image to load. So alert shows zero sizes.

DOMContentLoaded and scripts

When the browser initially loads HTML and comes across a <script>...</script> in the text, it can’t continue building DOM. It must execute the script right now. So DOMContentLoaded may only happen after all such scripts are executed.

External scripts (with src) also put DOM building to pause while the script is loading and executing. So DOMContentLoaded waits for external scripts as well.

The only exception are external scripts with async and defer attributes. They tell the browser to continue processing without waiting for the scripts. This lets the user see the page before scripts finish loading, which is good for performance.

Scripts with async and defer

Attributes async and defer work only for external scripts. They are ignored if there’s no src.

Both of them tell the browser that it may go on working with the page, and load the script “in background”, then run the script when it loads. So the script doesn’t block DOM building and page rendering.

There are two differences between them.

Difference In async defer
Order Scripts with async execute in the load-first order. Their document order doesn’t matter – which loads first runs first. Scripts with defer always execute in the document order (as they go in the document).
DOMContentLoaded Scripts with async may load and execute while the document has not yet been fully downloaded. That happens if scripts are small or cached, and the document is long enough. Scripts with defer execute after the document is loaded and parsed (they wait if needed), right before DOMContentLoaded event occurs.

So async is used for independent scripts, like counters or ads, that don’t need to access page content. And their relative execution order does not matter.

While defer is used for scripts that need DOM and/or their relative execution order is important.

DOMContentLoaded and styles

External style sheets don’t affect DOM, as DOM is built using only HTML, and that's why DOMContentLoaded does not wait for them. But there’s a pitfall: if we have a script after the style, then that script must wait for the stylesheet to execute:

<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // the script doesn't not execute until the stylesheet is loaded
  alert(getComputedStyle(document.body).marginTop);
</script>

The reason is that the script may want to get coordinates and other style-dependent properties of elements, like in the example above. Naturally, it has to wait for styles to load.

As DOMContentLoaded waits for scripts, it now waits for styles before them as well.

window.onload

The load event on the window object triggers when the whole page is loaded including styles, images and other resources. The example below correctly shows image sizes, because window.onload waits for all images:

<script>
  window.onload = function() {
    alert('Page loaded');

    // image is loaded at this time
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  };
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

window.onunload

When a visitor leaves the page, the unload event triggers on window. We can do something there that doesn’t involve a delay, like closing related popup windows. But we can’t cancel the transition to another page. For that we should use another event – onbeforeunload.

<script type="text/javascript">
window.addEventListener("unload", function(){
	console.log("Close");
});
</script>

You won't be able to see the console print because after it do the job it immediately closes the browser tab. So No waiting is involved. Event if you alert something it won't be shown as it causes delay.

window.onbeforeunload

If a visitor initiated navigation away from the page or tries to close the window, the beforeunload event occurs.

window.onbeforeunload = function() {
  return "There are unsaved changes. Leave now?";
};

Try to reload the page or close the tab to see the effect.

readyState

What happens if we set the DOMContentLoaded handler after the document is loaded?

Naturally, it never runs. Becuase the document is already loaded and if we attach DOMContentLoaded after the document is loaded, the event never occurs.

There are cases when we are not sure whether the document is ready or not, for instance an external script with async attribute loads and runs asynchronously. Depending on the network, it may load and execute before the document is complete or after that, we can’t be sure. So we should be able to know the current state of the document.

The document.readyState property gives us information about it. There are 3 possible values:

So we can check document.readyState and setup a handler or execute the code immediately if it’s ready.

function work() { /*...*/ }

if (document.readyState == 'loading') {
  document.addEventListener('DOMContentLoaded', work);
} else {
  work();
}

document.readystatechange

There’s a readystatechange event that triggers when the readyState changes, so we can print all these states like this:

// current state
console.log(document.readyState);

// print state changes
document.addEventListener('readystatechange', () => console.log(document.readyState));

Resource Loading : onload

The browser allows to track the loading of external resources – scripts, iframes, pictures and so on. There are two events for it:

let script = document.createElement('script');

// can load any script, from any domain
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);

script.onload = function() {
  // the script creates a helper function "_"
  alert(_); // the function is available
};
let script = document.createElement('script');
script.src = "https://example.com/404.js"; // no such script
document.head.append(script);

script.onerror = function() {
  alert("Error loading " + this.src); // Error loading https://example.com/404.js
};

You can also use onerror and onload attribute on the element.

<script type="text/javascript" onload="scriptLoaded()" onerror="scriptLoadError()"></script>
<img src="" onload="imageLoaded" onerror="imageLoadError()">

function scriptLoaded(){
	// ....
}
function scriptLoadError(){
	// ...
}
function imageLoaded(){
	// ....
}
function imageLoadError(){
	// ...
}

These event also works for iframe element.