JavaScript Page:DOMContentLoaded, load, beforeunload, unload

Today, browsers sometimes suspend pages or abort them entirely, in case the system resources are limited. Luckily, there are modern lifecycle hooks that help to handle such interventions without affecting the user experience.

There are three significant events in the framework of an HTML page lifecycle:

  • DOMContentLoaded: the event when the browser completely loaded HTML, the DOM tree is set up, but external resources, such as pictures <img> and stylesheets might not be loaded yet.
  • load: the event when not only HTML is loaded, but other external resources ( for instance, images, stylesheets and so on), too.
  • beforeunload: the event when the user is leaving the page.

Each of the events above may be useful on a specific purpose:

  • DOMContentLoaded: when the DOM is ready, the handler is capable of looking up DOM nodes and initializing the interface.
  • load: the external resources are loaded, hence the styles are applied, the sizes of the images are known and so on.
  • beforeunload: the user is getting out, and it’s not possible to check if the latter has saved the changes, and ask to make sure the user really wants to leave or not.
  • unload: the user has almost left, yet several operations can not be initiated ( for example, sending out statistics).

Further, let’s get into the details.

DOMContentLoaded

This event occurs on the document object.

The addEventListener should be used to catch it, like this:

document.addEventListener("DOMContentLoaded", ready);
// not a "document.onDOMContentLoaded = ..."

Here is an extensive example of using addEventListener:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <img id="img" src="/uploads/media/default/0001/05/e9f3899d915c17845be51e839d5ba238f0404b07.png">
    <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>
  </body>
</html>

In the example above, the DOMContentLoaded runs once the document is loaded. So, it is capable of seeing all the elements, including <img>.

But, note that it never waits for the image to load. Hence, the alert will show zero sizes.

The DOMContentLoaded event looks very simple at first sight. But, the event comes when the DOM tree is ready.

However, few peculiarities exist that we are going to cover further.

DOMContentLoaded and Scripts

Once the document processes an HTML-document and passes through a <script> tag, it should execute before continuing to set up the DOM. It’s like a precaution because scripts might want to modify the DOM, and, moreover, document.write into it. Therefore, the DOMContentLoaded should wait.

So, the DOMContentLoaded event occurs after scripts like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
  </head>
  <body>
    <script>
      document.addEventListener("DOMContentLoaded", () => {
        alert("DOM ready!");
      });
      alert("Library are loaded, inline script are executed");
    </script>
  </body>
</html>

As you can notice from the example above, first comes “Library loaded…” , only then “DOM ready”.

Please, note that there are exceptions to this rule. That is to say, the scripts with async attribute never block DOMContentLoaded. The scripts that are created dynamically using document.createElement('script') and added to the webpage after, don’t block this event, either.

DOMContentLoaded and Styles

The DOM is not affected by external style sheets, so DOMContentLoaded doesn’t wait for them.

But there is a drawback here. If there is a script after the style, then the script shall wait till the stylesheet is loading, like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
  </head>
  <body>
    <script>
      // the script will not execute till the stylesheet is loaded
      alert(getComputedStyle(document.body).marginTop);
    </script>
  </body>
</html>

That happens because the script might want to get coordinates or other style-dependant properties. So, it should wait for the style to load. As though DOMContentLoaded waits for the scripts, it will wait for the styles before them, too.

Built-in Browser Autofill

Chrome, Opera and Firefox can autofill forms on DOMContentLoaded. For example, if there is a page form with a login and password, and the browser remembered the values, then it might try to autofill them on DOMContentLoaded (it should be approved by the user).

Moreover, if DOMContentLoaded is suspended by the long-load scripts, the autofill should also wait. In some sites (in case of using browser autofill) the fields of login and password don’t get auto-filled at once. There is a delay until the page is completely loaded. That’s the delay till the DOMContentLoaded event.

Window.onload

Now, let’s speak about the load event. This event on the window object triggers when the whole page is loaded, involving images, styles, and other resources. The load event is available via the onload property.

In the example below, you can see the image sizes, as window.onload waits for the overall images:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
  </head>
  <body>
    <script>
      window.onload = function() { // same as window.addEventListener('load', (event) => {
        alert('Page loaded');
        // image loaded at this time
        alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
      };
    </script>
    <img id="img" src="/uploads/media/default/0001/05/e9f3899d915c17845be51e839d5ba238f0404b07.png">
  </body>
</html>

Window.onunload

The unload event triggers on the window when a visitor leaves the page. You can do there something that doesn’t include a delay (for example, closing related popup window). Sending analytics is considered a notable exception.

Imagine, you want to gather data about how the page is used: scrolls, mouse clicks, and so on. As a rule, the unload event is when the user leaves the page, and you want to save the data on the server. A unique navigator.sendBeacon(url, data) method exists for such needs. It can send the data to the background. Also, there is no delay in the transition to another page still performing sendBeacon.

Here is an example of using sendBeacon:

let analyticsData = { /* object with gathered data */ };
window.addEventListener("unload", function () {
      navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
    };

So, in the example above:

  1. The request is forwarded as POST.
  2. It is possible to send not only a string but also forms and other formats.
  3. There is a data limit: 64kb.

Once the sendBeacon request is over, the browser has probably left the document. Therefore, there is no way of getting server response (for analytics, it’s usually empty).

Also, you can use keepalive to perform “after-page-left” requests in the fetch method for generic network requests.

For canceling the transition to another page, you can use another event: onbeforeunload.

Window.onbeforeunload

If a user has initiated navigation away from the page or intends to close the window, the beforeunload will ask for additional confirmation. In case of discarding the event, the browser will ask the user whether they are sure. See how to do it by running the following code and reloading the page, as shown below:

window.onbeforeunload = function() {
       return false;
      };

An interesting thing to note: returning a non-empty string also counts as aborting the event. Previously, the browsers used to show it as a message, but the modern specification doesn’t allow that.

Let’s take a look at an example:

window.onbeforeunload = function () {
  return "Changes you made may not be saved. Leave site?";
};

The reason for changing the behaviour was that some webmasters abused the event handler by showing annoying messages. Still, old browsers may show messages, but there is no way of customizing the message.

ReadyState

The document.readyState property informs about the current loading state.

Three possible values can be distinguished:

  • "loading": the state of loading the document.
  • "interactive""interactive": the document is completely read.
  • "complete": the document is completely read, and all the resources are loaded.

So, you can check document.readyState and set up a handler, executing the code immediately when it’s ready.

Here is an example of using document.readyState:

function work() { /*...*/ }
if (document.readyState == 'loading') {
  // loading yet, wait for the event
  document.addEventListener('DOMContentLoaded', work);
} else {
  // DOM is ready!
  work();
}

You can also use the readystatechange event, which gets activated when the state changes. So, all the state can be printed as follows:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <script>
      // current state
      alert(document.readyState);
      // print state changes
      document.addEventListener('readystatechange', () => alert(document.readyState));
    </script>
  </body>
</html>

So, this event is an alternative way of tracking the document loading state. But, nowadays, it’s not used often.

The complete events flow will look like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <iframe src="iframe.html" onload="log('iframe onload')"></iframe>
    <img src="/uploads/media/default/0001/05/e9f3899d915c17845be51e839d5ba238f0404b07.png" id="img">
    <script>
      log('initial readyState:' + document.readyState);
      document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
      document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));
      window.onload = () => log('window onload');
      img.onload = () => log('img onload');
    </script>
  </body>
</html>

The example above includes <iframe>, <img>, as well as handlers for logging events.

Summary

Let’s sum up when the page load events trigger and what they can be useful for.

In brief, the page load events are the following:

  • The DOMContentLoaded event happens on the document when the DOM is ready. So, JavaScript can be applied to elements at this stage.
  • The load event on the window occurs once the page and all the resources are loaded. As a rule, it’s rarely used.
  • The beforeunload event is activated on the window when the user intends to leave the page. While canceling the event, the browser asks whether the user really wishes to leave.
  • The unload event triggers once the user is leaving.
  • And, finally, the document.readyState is the current state of the document. The changes can be tracked in the readystatechange in the following states: loading (the document is in the loading state), interactive ( the document is parsed), complete (the document and the resources are load).



Do you find this helpful?

Related articles