- Load
DOMContentLoaded- The alternative to
DOMContentLoaded - Hacks for
IE<9 - The crossbrowser
DOMContentLoadedhandling code - 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.
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
deferattribute.
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
BODYis not complete, sodocument.body.appendChildappends to current end ofBODY, right after theSCRIPT.
IE6 can’t do suchbody.appendChildat 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.