onLoad and onDOMContentLoaded

  1. Load
  2. DOMContentLoaded
    1. What it awaits, in detail
  3. The alternative to DOMContentLoaded
  4. Hacks for IE<9
    1. IE<9 hack for a document not inside a frame
    2. IE<9 hack in a frame
    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 IMG, IFRAME 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:

<iframe src="/"></iframe>
<script>
  document.getElementsByTagName('iframe')[0].onload = function() {
    alert('loaded')
  }
</script>

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:

<html>
<head>
<script>
  onload = function() {
    alert('loaded')
  }
</script>
</head>
<body>
  ... page content ...
</body>
</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 support it.

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 dynamic script) 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 a script.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 </BODY>:

<body>

... bla-bla-bla my cool interface...

  <script>
     init()
  </script>

</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

IE<9 hack for a document not inside a frame

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.

try {
  var isFrame = window.frameElement != null
} catch(e) {}

if (document.documentElement.doScroll && !isFrame) {
  function tryScroll(){
    if (called) return
    try {
      document.documentElement.doScroll("left")
      ready()
    } catch(e) {
      setTimeout(tryScroll, 10)
    }
  }
  tryScroll()
}

The function tryScroll is repeatedly called until there is no exception on doScroll("left").

IE<9 hack in a frame

For a document inside a frame or iframe, the doScroll trick doesn’t work, so we use a special IE event named document.onreadystatechange:

document.attachEvent("onreadystatechange", function(){
  if ( document.readyState === "complete" ) {
    ready()
  }
})

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.

if (window.addEventListener)
  window.addEventListener('load', ready, false)
else if (window.attachEvent)
  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.

function bindReady(handler){

	var called = false

	function ready() { 
		if (called) return
		called = true
		handler()
	}

	if ( document.addEventListener ) { // native event
		document.addEventListener( "DOMContentLoaded", ready, false )
	} else if ( document.attachEvent ) {  // IE

		try {
			var isFrame = window.frameElement != null
		} catch(e) {}

		// IE, the document is not inside a frame
		if ( document.documentElement.doScroll && !isFrame ) {
			function tryScroll(){
				if (called) return
				try {
					document.documentElement.doScroll("left")
					ready()
				} catch(e) {
					setTimeout(tryScroll, 10)
				}
			}
			tryScroll()
		}

		// IE, the document is inside a frame
		document.attachEvent("onreadystatechange", function(){
			if ( document.readyState === "complete" ) {
				ready()
			}
		})
	}

	// Old browsers
    if (window.addEventListener)
        window.addEventListener('load', ready, false)
    else if (window.attachEvent)
        window.attachEvent('onload', ready)
    else {
		var fn = window.onload // very old browser, copy old onload
		window.onload = function() { // replace by new onload and call the old one
			fn && fn()
			ready()
		}
    }
}

Multiple handlers

The bindReady allows to hook a single handler only. To attach multiple handlers, we need an external wrapper:

var readyList = []

function onReady(handler) {
	
	function executeHandlers() {
		for(var i=0; i<readyList.length; i++) {
			readyList[i]()
		}
	}

	if (!readyList.length) { // set handler on first run 
		bindReady(executeHandlers)
	}

	readyList.push(handler)
}

An example of use:

<!DOCTYPE HTML>
<html>
<body>

<p>Large image will load after initialization</p>

<script src="bindready.js"></script>
<script src="onready.js"></script>

<script>
onReady(function() {                                               
  document.body.appendChild(document.createTextNode('init 1! '))
})

onReady(function() {                                               
  document.body.appendChild(document.createTextNode('init 2! '))
})
</script>

<script>
// prevent caching with random param
document.write('<img src="web.jpg?nocache='+Math.random()+'" width="200">')
</script>

</body>
</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.

Tutorial

Donate

Donate to this project