DOMContentLoaded vs jQuery.ready vs onload, How To Decide When Your Code Should Run

At a Glance

Script tags have access to any element which appears before them in the HTML. jQuery.ready / DOMContentLoaded occurs when all of the HTML is ready to interact with, but often before its been rendered to the screen. The load event occurs when all of the HTML is loaded, and any subresources like images are loaded. Use setTimeout to allow the page to be rendered before your code runs.

Deep Dive

The question of when your JavaScript should run comes down to ‘what do you need to interact with on the page?’.

Scripts have access to all of the elements on the page which are defined in the HTML file before the script tag. This means, if you put your script at the bottom of the <body> you know every element on the page will be ready to access through the DOM:

<html>
  <body>
    <div ></div>

    <script>
      document.querySelector('#my-awesome-el').innerHTML = new Date
    </script>
  </body>
</html>

This works just as well for external scripts (specified using the src attribute).

If, however, your script runs before your element is defined, you’re gonna have trouble:

<html>
  <body>
    <script>
      document.querySelector('#my-awesome-el').innerHTML = new Date
      /* ERROR! */
    </script>

    <div ></div>
  </body>
</html>

There’s no technical difference between including your script in the <head> or <body>, all that matters is what is defined before the script tag in the HTML.

When All The HTML/DOM Is Ready

If you want to be able to access elements which occur later than your script tag, or you don’t know where users might be installing your script, you can wait for the entire HTML page to be parsed. This is done using either the DOMContentLoaded event, or if you use jQuery, jQuery.ready (sometimes referred to as $.ready, or just as $()).

<html>
  <body>
    <script>
      window.addEventListener('DOMContentLoaded', function(){
        document.querySelector('#my-awesome-el').innerHTML = new Date
     });
    </script>

    <div ></div>
  </body>
</html>

the same script using jQuery:

jQuery.ready(function(){
  document.querySelector('#my-awesome-el').innerHTML = new Date
});

// OR

$(function(){
  document.querySelector('#my-awesome-el').innerHTML = new Date
});

It may seem a little odd that jQuery has so many syntaxes for doing the same thing, but that’s just a function of how common this requirement is.

Run When a Specific Element Has Loaded

DOMContentLoaded/jQuery.ready often occurs after the page has initially rendered. If you want to access an element the exact moment it’s parsed, before it’s rendered, you can use MutationObservers.

var MY_SELECTOR = ".blog-post" // Could be any selector

var observer = new MutationObserver(function(mutations){
  for (var i=0; i < mutations.length; i++){
    for (var j=0; j < mutations[i].addedNodes.length; j++){
      // We're iterating through _all_ the elements as the parser parses them,
      // deciding if they're the one we're looking for.
      if (mutations[i].addedNodes[j].matches(MY_SELECTOR)){
        alert("My Element Is Ready!");

        // We found our element, we're done:
        observer.disconnect();
      };
    }
  }
});

observer.observe(document.documentElement, {
  childList: true,
  subtree: true
});

As this code is listening for when elements are rendered, the MutationObserver must be setup before the element you are looking for in the HTML. This commonly means setting it up in the <head> of the page.

For more things you can do with MutationObservers, take a look at our article on the topic.

Run When All Images And Other Resources Have Loaded

It’s less common, but sometimes you want your code to run when not just the HTML has been parsed, but all of the resources like images have been loaded. This is the time the page is fully rendered, meaning if you do add something to the page now, there will be a noticable ‘flash’ of the page before your new element appears.

window.addEventListener('load', function(){
  // Everything has loaded!
});

Run When A Specific Image Has Loaded

If you are waiting on a specific resource, you can bind to the load event of just that element. This code requires access to the element itself, meaning it should appear after the element in the HTML source code, or happen inside a DOMContentLoaded or jQuery.ready handler function.

document.querySelector('img.my-image').addEventListener('load', function(){
  // The image is ready!
});

Note that if the image fails to load for some reason, the load event will never fire. You can, however, bind to the error event in the same manner, allowing you to handle that case as well.

Run When My Current Changes Have Actually Rendered

Changes you make in your JavaScript code often don't actually render to the page immediately. In the interest of speed, the browser often waits until the next event loop cycle to render changes. Alternatively, it will wait until you request a property which will likely change after any pending renders happen.. If you need that rendering to occur, you can either wait for the next event loop tick;

setTimeout(function(){
  // Everything will have rendered here
});

Or request a property which is known to trigger a render of anything pending:

el.offsetHeight // Trigger render

// el will have rendered here

The setTimeout trick in particular is also great if you’re waiting on other JavaScript code. When your setTimeout function is triggered, you know any other pending JavaScript on the page will have executed.

栏目
文章分类