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 id="my-awesome-el"></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 id="my-awesome-el"></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 id="my-awesome-el"></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 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.