onLoad and onDOMContentLoaded
- Load
DOMContentLoaded
- The alternative to
DOMContentLoaded
- Hacks for
IE<9 code="">9>
- The crossbrowser
DOMContentLoaded
handling 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 SCRIPT
and 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
:1 | < iframe src = "/" ></ iframe > |
2 |
|
3 | document.getElementsByTagName( 'iframe' )[0].onload = function () { |
4 | alert( 'loaded' ) |
5 | } |
6 |
|
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 |
|
04 | onload = function () { |
05 | alert( 'loaded' ) |
06 | } |
07 |
|
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="">9>
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 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
1 | < body > |
2 |
3 | ... bla-bla-bla my cool interface... |
4 |
5 |
|
6 | init() |
7 |
|
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, sodocument.body.appendChild
appends to current end ofBODY
, right after theSCRIPT
.
IE6 can’t do suchbody.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="">9>
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.
01
try
{
02
var
isFrame = window.frameElement !=
null
03
}
catch
(e) {}
04
05
if
(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
:
1
document.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.
1
if
(window.addEventListener)
2
window.addEventListener(
'load'
, ready,
false
)
3
else
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.
01
function
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:
01
var
readyList = []
02
03
function
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
11
onReady(
function
() {
12
document.body.appendChild(document.createTextNode(
'init 1! '
))
13
})
14
15
onReady(
function
() {
16
document.body.appendChild(document.createTextNode(
'init 2! '
))
17
})
18
19
20
21
// prevent caching with random param
22
document.write(
'+Math.random()+
'" width="200">'
)
23
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.
9>
9>
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.01 | try { |
02 | var isFrame = window.frameElement != null |
03 | } catch (e) {} |
04 |
05 | if (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 | } |
tryScroll
is repeatedly called until there is no exception on doScroll("left")
.
For a document inside a frame or iframe, the
doScroll
trick doesn’t work, so we use a special IE event named document.onreadystatechange
:1 | document.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.1 | if (window.addEventListener) |
2 | window.addEventListener( 'load' , ready, false ) |
3 | else 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.01 | function 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:01 | var readyList = [] |
02 |
03 | function 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 |
|
11 | onReady( function () { |
12 | document.body.appendChild(document.createTextNode( 'init 1! ' )) |
13 | }) |
14 |
15 | onReady( function () { |
16 | document.body.appendChild(document.createTextNode( 'init 2! ' )) |
17 | }) |
18 |
|
19 |
20 |
|
21 | // prevent caching with random param |
22 | document.write( '+Math.random()+ '" width="200">' ) |
23 |
|
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
9>DOMContentLoaded
using methods described above.