onLoad and onDOMContentLoaded

11:49:00 AM 0 Comments

  1. Load
  2. DOMContentLoaded
    1. What it awaits, in detail
  3. The alternative to DOMContentLoaded
  4. Hacks for IE<9 code="">
    1. IE<9 a="" document="" for="" frame="" hack="" inside="" not="">
    2. IE<9 a="" frame="" hack="" in="">
    3. The last resort: window.onload
  5. The crossbrowser DOMContentLoaded handling code
  6. Multiple handlers
The “document is loaded” event is a nice hook for page initialization. All elements are at place and we can build interfaces on them.
There are two main events to track it: load and DOMContentLoaded.

Load

The load event is a general “loading complete” signal. It is supported by many elements. For example, external SCRIPTand IMGIFRAME trigger it when downloading of their content finishes.
The handler window.onload and iframe.onload triggers when the page is fully loaded with all dependent resources including images and styles.
The example with IFRAME:
1<iframe src="/"></iframe>
2
To execute the code when the current page finishes loading, use window.onload.
For the bage below, the alert shows up when it is completely loaded, including the BODY and all resources:
01<html>
02<head>
03
08</head>
09<body>
10  ... page content ...
11</body>
12</html>
window.onload is rarely used, because no one wants to wait until all resources load, especially for large pictures.
Normally, we need the DOM and scripts to build interfaces. That’s exactly what DOMContentLoaded is for.

DOMContentLoaded

The DOMContentLoaded event triggers on document when the page is ready. It waits for the full HTML and scripts, and then triggers.
All browsers except IE<9 br="" it.="" support="">
document.addEventListener( "DOMContentLoaded", ready, false )
The ready function is a handler which usually performs interface initialization.
About IE hacks - we’ll get to them later.
Firefox doesn't autofill forms before `DOMContentLoaded`
For example, you have a login/password form, and the values are remembered by Firefox.
The browser will autofill them only after DOMContentLoaded. If it takes too long, the user may have to wait.

What it awaits, in detail

Generally, the DOMContentLoaded awaits only for HTML and scripts. But there are many peculiar details about that.
Not taking them into account may make DOMContentLoaded trigger later than needed, forcing the visitors to wait. Or, it may trigger earlier and skip strictly required resouces.
DOMContentLoaded doesn’t wait for any script.
There are ways to add a script to the document, so that DOMContentLoaded won’t wait for them.
You may use them smartly, to make it trigger early. Or hit that feature occasionaly, and try to initialize interfaces without a script.
DOMContentLoaded won’t wait for a script, created by document.createElement (called dynamicscript) in all browsers except Opera.
var script = document.createElement('script')
script.src = "..."
document.getElementsByTagName('head')[0].appendChild(script)
This feature is used to add ads and counters which don’t block the page from initialization. Usually ascript.async = true is added to make the script don’t wait for other scripts.
DOMContentLoaded will wait for a script:
  • In all browsers - external scripts in HTML.
  • In Opera - all scripts.
  • In Safari/Chrome - scripts with defer attribute.
And of course, DOMContentLoaded triggers after all inline scripts are executed. The browser can’t render a page without it.

The alternative to DOMContentLoaded

The most obvious way to execute JavaScript after the page load is to put it before the 
:
1<body>
2 
3... bla-bla-bla my cool interface...
4 
5  
8 
9</body>
That’s simple. And the simplicity rules. But the drawbacks are:
  • You must put JS inside HTML. That makes integration more difficult for third-party plugins and components.
  • The BODY is not complete, so document.body.appendChild appends to current end of BODY, right after the SCRIPT.
    IE6 can’t do such body.appendChild at all.
The real document.DOMContentLoaded event ensures that the DOM is ready including BODY and everything. It can be used in JS without modifying HTML.

Hacks for IE<9 code="">

IE<9 a="" document="" for="" frame="" h3="" hack="" inside="" not="">
For IE, there is a hack which works if a window is not inside a frame.
The document is being scrolled using document.documentElement.doScroll call. The browser throws exception until the DOM is complete. So basically the scoll is called every 10 ms or so until no exception is thrown. Then the DOM ready handler is activated.
01try {
02  var isFrame = window.frameElement != null
03catch(e) {}
04 
05if (document.documentElement.doScroll && !isFrame) {
06  function tryScroll(){
07    if (called) return
08    try {
09      document.documentElement.doScroll("left")
10      ready()
11    catch(e) {
12      setTimeout(tryScroll, 10)
13    }
14  }
15  tryScroll()
16}
The function tryScroll is repeatedly called until there is no exception on doScroll("left").

IE<9 a="" frame="" h3="" hack="" in="">
For a document inside a frame or iframe, the doScroll trick doesn’t work, so we use a special IE event named document.onreadystatechange:
1document.attachEvent("onreadystatechange"function(){
2  if ( document.readyState === "complete" ) {
3    ready()
4  }
5})
The event triggers many times in the process of document loading. It is very buggy and unreliable, but if it triggers with readyState=="complete", it means that the document is really complete.
Unfortunately, this also requires all resources to be loaded: images, styles etc.

The last resort: window.onload

There are non-IE browsers which do not support DOMContentLoaded, just because they are old. Still we need to support them somehow.
The window.onload is an ancient event which triggers on full page load. So we can bind to it as well.
1if (window.addEventListener)
2  window.addEventListener('load', ready, false)
3else if (window.attachEvent)
4  window.attachEvent('onload', ready)
Actually, the onreadystatechange with complete state for IE triggers just before onload.

The crossbrowser DOMContentLoaded handling code

The following code joins the methods described above into a single bindReady(handler) method.
The ready handler is bound in multiple ways, but ensures that only the first trigger will work.
01function bindReady(handler){
02 
03    var called = false
04 
05    function ready() {
06        if (called) return
07        called = true
08        handler()
09    }
10 
11    if ( document.addEventListener ) { // native event
12        document.addEventListener( "DOMContentLoaded", ready, false )
13    else if ( document.attachEvent ) {  // IE
14 
15        try {
16            var isFrame = window.frameElement != null
17        catch(e) {}
18 
19        // IE, the document is not inside a frame
20        if ( document.documentElement.doScroll && !isFrame ) {
21            function tryScroll(){
22                if (called) return
23                try {
24                    document.documentElement.doScroll("left")
25                    ready()
26                catch(e) {
27                    setTimeout(tryScroll, 10)
28                }
29            }
30            tryScroll()
31        }
32 
33        // IE, the document is inside a frame
34        document.attachEvent("onreadystatechange"function(){
35            if ( document.readyState === "complete" ) {
36                ready()
37            }
38        })
39    }
40 
41    // Old browsers
42    if (window.addEventListener)
43        window.addEventListener('load', ready, false)
44    else if (window.attachEvent)
45        window.attachEvent('onload', ready)
46    else {
47        var fn = window.onload // very old browser, copy old onload
48        window.onload = function() { // replace by new onload and call the old one
49            fn && fn()
50            ready()
51        }
52    }
53}

Multiple handlers

The bindReady allows to hook a single handler only. To attach multiple handlers, we need an external wrapper:
01var readyList = []
02 
03function onReady(handler) {
04     
05    function executeHandlers() {
06        for(var i=0; i
07            readyList[i]()
08        }
09    }
10 
11    if (!readyList.length) { // set handler on first run
12        bindReady(executeHandlers)
13    }
14 
15    readyList.push(handler)
16}
An example of use:
01
02<html>
03<body>
04 
05<p>Large image will load after initialization</p>
06 
07
08
09 
10
19 
20
24 
25</body>
26</html>
You can view the live example and export all sources here:tutorial/browser/events/domcontentloaded/index.html.
Most modern frameworks support DOMContentLoaded using methods described above.