Contents
The minds : who has been working on this?
The solution : what did they come up with?
The problem : pictures
If you are using JavaScript to do things to the HTML of your page, then one of the problems you may occasionally encounter is detecting when all of your HTML has loaded, but before pictures. Why would you want to do this? You may want to start manipulating the DOM using JavaScript as soon as possible, particularly if you are loading a lot of pictures.
You might expect to be able to use window.onload
thus:-
function loadHandler()
{
var element /*: HTMLElement*/ = document.getElementById("someElementID");
// Do something very clever with that element.
}
window.onload = loadHandler;
The problem is, that it does not fire until all of the pictures (and indeed any other content) have loaded. This means that if you have a lot of pictures, there may be a delay before your JavaScript starts making the page look lovely.
So what you may want is a way to determine when the DOM has fully loaded without waiting for all those pesky images to load as well.
The minds : who has been working on this?
An awful lot of work has gone into this from many people, and I would particularly recommend the following:-
The solution : what did they come up with?
The solution seems to have centered on the following:-
Internet Explorer
Internet Explorer's <script>
element supports the defer
attribute. This means that IE will defer the loading of the script until after the DOM has loaded. The solution therefore is to insert such a script and use its onreadystatechange
feature to detect when the script element has completed loading.
There are a number of issues relating to the src
element and the https
protocol discussed by Mathias Miller and Rob Cherney.
Mozilla/Firefox, and Opera 9
These both provide a special event on the document called DOMContentLoaded
.
KHTML/Safari/WebKit
This seems to involve testing the document.readyState
property, which is apparently reliable in these browsers, but not consistent cross-browser.
Other Browsers
One seems to be stuck with window.onload
.
The solution : what does it look like?
I have reproduced/represented all of this work in a single function OnDOMLoad
, which looks like this:-
/**
* (JavaScript Function)
*
* OnDOMLoad (Community Version)
*
* June 2007
*
* A function which detects when all of
* the HTML in a document has completed loading (before pictures),
* and fires the user supplied "eventHandler" function.
*
* Credit for all of the work should go to Dean Edwards
* <http://dean.edwards.name/weblog/2006/06/again/> and
* the credits he gives there.
*
* @param (Function) eventHandler
* A 'Function' reference to the function you want
* to be called when the DOM is fully loaded.
*
* @param (Window) targetWindow (Optional)
* A DOM Window object reference, if you want to
* target a window other than the current window
*
* @return (void)
* No return value.
*/
function OnDOMLoad (
eventHandler /*: Function*/,
targetWindow /*: Window*/
) /*: void*/
{
var domLoaded /*: Boolean*/ = false;
var isIE /*: Boolean*/ = false;
var isMozillaOrOpera9 /*: Boolean*/ = false;
var isKHTML /*: Boolean*/ = false;
var isOther /*: Boolean*/ = false;
var interval /*: HostObject*/ = null;
var targetDocument /*: Document*/ = null;
if (typeof eventHandler != "function")
{
return;
}
if (typeof targetWindow == "undefined")
{
targetWindow = window;
targetDocument = window.document;
}
else
{
targetDocument = targetWindow.document;
}
OnDOMLoad_BrowserSniff();
if (isMozillaOrOpera9)
{
targetDocument.addEventListener("DOMContentLoaded", OnDOMLoad_Loaded, null);
}
else if (isIE)
{
var src /*: String*/ = "src='javascript:void(0)'";
if (targetDocument.location.protocol == "https:")
{
src = "src=//0";
}
targetDocument.write("<script id='DOMLoadScript' defer " + src + "><\/script>");
var scriptElement /*: HTMLScriptElement*/ = targetDocument.getElementById("DOMLoadScript");
scriptElement.onreadystatechange = function()
{
if (scriptElement.readyState == "complete")
{
/* Possible memory leakage?*/
scriptElement.onreadystatechange = null;
scriptElement.removeNode(true);
OnDOMLoad_Loaded();
}
};
}
else if (isKHTML)
{
interval = targetWindow.setInterval(function()
{
if (/loaded|complete/.test(targetDocument.readyState))
{
OnDOMLoad_Loaded();
}
}, 10);
return;
}
/* for other browsers - no solution*/
else
{
targetWindow.onload = OnDOMLoad_Loaded;
}
function OnDOMLoad_BrowserSniff()
{
/*@cc_on @*/
/*@if (@_jscript_version >= 0)
isIE = true;
return;
@end @*/
if (/KHTML|WebKit/i.test(navigator.userAgent))
{
isKHTML = true;
}
else if (targetDocument.addEventListener)
{
isMozillaOrOpera9 = true;
}
else
{
isOther = true;
}
}
function OnDOMLoad_Loaded()
{
if (domLoaded)
{
return;
}
domLoaded = true;
targetWindow.clearInterval(interval);
interval = null;
/* Success!*/
eventHandler();
}
}
Any Alternative?
Dummy Element
There are lots of alternatives that have been suggested, including inserting a dummy element as the last childNode in the <body></body>
and testing for its presence.
onactivate
Internet Explorer seems to support this event on the document
. I have not tested this.
Element Count
My additional suggestion, as a possible back-up, is to set a timer running to detect the number of parsed DOM elements currently available, and, if this remains unchanged for, say, 1/10th of a second, then assume that the DOM has finished loading.
It looks like this:-
/**
* (JavaScript Function)
*
* OnDOMLoad (Element Count Version)
*
* June 2007
*
* A function which detects when all of
* the HTML in a document has completed loading (before pictures),
* and fires the user supplied "eventHandler" function.
*
* Written by Julian Turner 2007
* Copyright Free
*
* @param (Function) eventHandler
* A 'Function' reference to the function you want
* to be called when the DOM is fully loaded.
*
* @param (Window) targetWindow (Optional)
* A DOM Window object reference, if you want to
* target a window other than the current window
*
* @return (void)
* No return value.
*/
function OnDOMLoad (
eventHandler /*: Function*/,
targetWindow /*: Window*/
) /*: void*/
{
var domLoaded /*: Boolean*/ = false;
var targetDocument /*: Document*/ = null;
if (typeof eventHandler != "function")
{
return;
}
if (typeof targetWindow == "undefined")
{
targetWindow = window;
targetDocument = window.document;
}
else
{
targetDocument = targetWindow.document;
}
var hasAll = (typeof targetDocument.all != "undefined");
var hasTag = (typeof targetDocument.getElementsByTagName != "undefined");
if (!hasAll && !hasTag)
{
targetWindow.onload = OnDOMLoad_Loaded;
return;
}
/**
* Values are in Milliseconds
*
* noChangePeriod
* How long to wait before deciding all has loaded
*
* sampleRate
* How many times to sample the element count during the
* noChangePeriod
**/
var noChangePeriod /*: int*/ = 100;
var sampleRate /*: int*/ = 10;
var lastCount /*: int*/ = 0;
var sampleInterval /*: int*/ = noChangePeriod / sampleRate;
var sampleCount /*: int*/ = sampleRate;
interval = targetWindow.setInterval(function()
{
if (!targetDocument.body)
{
return;
}
/* targetDocument.all is a hack for IE5 */
if (hasAll)
{
var currentCount /*: int*/ = targetDocument.all.length;
}
else
{
var currentCount /*: int*/ = targetDocument.getElementsByTagName("*").length;
}
if (currentCount == 0)
{
return;
}
if (lastCount == 0)
{
lastCount = currentCount;
return;
}
if (lastCount === currentCount)
{
if (!sampleCount--)
{
OnDOMLoad_Loaded();
}
return;
}
lastCount = currentCount;
sampleCount = sampleRate;
}, sampleInterval);
function OnDOMLoad_Loaded()
{
if (domLoaded)
{
return;
}
domLoaded = true;
targetWindow.clearInterval(interval);
interval = null;
/* Success!*/
eventHandler();
}
}
Its main weakness is that there is a slight delay before your script is activated, due to the timer, and there is a risk that more than 100 m/s may pass between loading of one element and the next.
It also uses getElementsByTagName
. This is not compatible with IE5 and so a small object detection is needed for IE5: element.all || element.getElementsByTagName('*')
.
Test Pages
Here are my test pages:-
Dean Edwards' Inspired Version
Sorry, comments have been suspended. Too much offensive comment spam is causing the site to be blocked by firewalls (which ironically therefore defeats the point of posting spam in the first place!). I don't get that many comments anyway, so I am going to look at a better way of managing the comment spam before reinstating the comments.