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.
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.
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.
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">
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.
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.
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:
loading
: the document is loading.interactive
: the document was fully read.complete
: the document was fully read and all resources (like images) are loaded too.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(); }
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));
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.