Post Archive

› August 28, 2005

Breaking onload limits

  • Reported by Alessandro

Every javascript coder, in almost every script, has encountered the onload limits. In this article we'll present them briefly, together with some solutions. The window.onload event handler has two main limits:

  1. You can attach just one function to it
  2. The script will wait until the page is fully loaded before running

Limit number one forces the coder to have a centralized function that calls all the functions that we want to run, and this goes against script independence; limit number two causes an annoying visual delay in script that changes the look and feel of the page.

In order to assign more than one function to one event handler, Scott Andrew LePera (a while ago) outlined a solution with listeners in Crossbrowser DOM Scripting: Event Handlers. You can find a possible application of the script on Enhancing Structural Markup with JavaScript by Simon Willison.

Later on, Willison elaborated a way to attach multiple functions to the onload handler that uses closures: you can read it detailed in Executing JavaScript on page load and explained further in Closures and executing JavaScript on page load.

Using event handlers is a nice way to set more than one function to onload, but this still implies that the scripts start just when the page, with images included, has totally loaded. So, let's work on the second problem. The solutions to limit the visual delay of unscripted content are mainly three.

The first solution is to embed the script at the bottom of the markup before the closure of the body tag instead of putting it in the head section. It simply won't use the onload handler, and will start run as soon as the markup is loaded, so it won't have to wait images or objects. This solution is one of the easiest, but has three disadvantages: the whole markup must be loaded (and we still get probably a bit of visual latency) and, more important, this goes against the separation of content and behaviour. Third, every call to the entry point functions of the page still must be centralized in that script.

The second solution can be applied in some cases, and it works with the aid of CSS to reduce to zero the visual lag. It still uses onload, but it very elegantly gets rid of that flicker on page load. The core point is to embed the initial state of the functional CSS of the page throught a simple document.write that will do the trick. An excellent explanation of this technique is Unobtrusive show/hide behavior reloaded by Bobby Van der Sluis.

In general, a similar approach could be applied to hide something that's going to be changed, transformed or made interactive while the page is loading, but there are two little things to consider. It won't work on XML documents and XHTML pages served with mime types of application/xhtml+xml, (I don't believe at the current state of play this is a real matter... anyway, you can discover the reason here); and, most important, you should know that display:none has some accessibility issues with screen readers.

The third kind of solution is to use timers to loop at predefined intervals until the element(s) we're going to work on are available on the DOM tree. This approach overcomes the limit of onload event handler, since it doesn't use it, and reduces significantly the latency of the script. An approach of that kind could be found in JavaScript load placement by Cameron Adams, in wich he presents a simple function that loops at a predefined interval until an element with a given id is available, and then calls the real function that must be applied on the element. If I may share an opinion: the script is concise and effective, but I feel that a time interval of 1 millisecond could be too short, since almost no data are transferred to the browser in that lapse, and hence resource consuming. Perhaps a value between 20 and 200ms could be better.

More complex but at the same time versatile solutions with timers could be found in DomFunction by BrotherCake and onDOMLoad by Aaron Barker. The first script allows javascript as soon the body element is available in the DOM, and optionally you can specify a determined id or a tag name. The second script is able to run javascript as soon as a set elements, targeted by a subset of CSS selectors, is available in the DOM.

Recently in some of my Italian articles I've been playing with timers, for instance in a javascript image replacement technique and on a script to get a random image on the header of the page, realizing that they overcome quite well the two main limits of onload. They allow a total script separation, making each script plug-and-run, and they enable the script nearly as soon as possible.

A timer based approach still presents a little flickering of unscripted content and if this is a major downside, you can easily solve the problem by initially hiding, or better shifting off the page, the elements you're going to script with document.write("some css") and then eventually making them visible again when they've been processed. I've used this approach on a very simple slideshow system with improved result for the user point of view, and I believe that could be applied to a very large set of scripts.

That's all for now, but I hopefully believe further solutions will come... there's surely something just waiting to be discovered, experimented and presented.

Comments

1. August 29, 2005 12:31 AM

Quote this comment

stylo~ Posted…

There's also the mozilla only DOMContentLoaded event. I don't see any of these scripts using that easy shortcut. See http://dean.edwards.name/weblog/2005/02/order-of-events/.

2. August 29, 2005 11:47 AM

Quote this comment

Cameron Adams Posted…

Page flicker for CSS adjusting JavaScript can be greatly reduced by implementing a JavaScript-only stylesheet. This can be implemented before the <head> tag has been closed (so well before the content has loaded), and can be simply done by switching on a linked stylesheet using JavaScript -- you then know that the person has JavaScript on, so it's safe to hide various elements in the CSS, etc.

From memory, by positioning the switching JavaScript after the <link> tag, you'll be able to access the properties of the stylesheet to switch it on. But if not, you can just use an onload timer for the particular stylesheet.

3. August 29, 2005 03:44 PM

Quote this comment

Mark Wubben Posted…

Unfortunately sometimes you do need to wait until `onload`. Safari for instance won't lay out the page correctly until it's been fully loaded, which means that for sIFR we had to do a special check for Safari so we could replace the elements earlier in other browsers. This problem is comparable to the non-incremental rendering of XHTML pages by Safari and Mozilla.

4. August 29, 2005 07:58 PM

Quote this comment

Ben Posted…

Use a timer and search innerHTML for </body>. :P

5. August 29, 2005 08:03 PM

Quote this comment

Emrah BASKAYA Posted…

I was thinking of a timer based wait-for-object system too, but I never needed it vitally. I may program myself one anytime soon, tho.

It is also worth mentioning, that IE crashes the page if you do Dom Tree changing on elements whose tags are not closed (e.g. adding an element to the body tag while page loads), but you can do it for elements with closed tags. Other leading browsers have no problems attaching elements even during page load. For these situations when you need to attach an element during load to the body, whose tags would indeed be not closed, you would have to use document.write. If you need to place the object during load in a child element of body, all dom operations seem to work fine within that object, if you place your inline script after its closed tag.

My navigation system uses an inline script to change the class of the top navigation links, right after its container tags are closed. The change is not visible during a typical loading session. It provides a simple drop down menu during load until the cuter, animated version can be attached using ordinary dom methods, and the site is still navigatable with javascript off, albeit it doesn't look as nice, obviously. Changing class is a nice trick, you don't have to use document.write to stuck in some CSS then (although I use document.write for other reasons in the navigation script, that IE limitation in particular).

6. August 30, 2005 06:22 AM

Quote this comment

atnbueno Posted…

I've recently rewritten most of the javascript in my site. After struggling about the onload issue, now I load the script/s in the "head" and call DOM functions in another "script" tag just before the "/body". I use global and section headers and footers, so the code is quite clean, and it works even when I press the Back button in Opera.

The visual impact is minimal, as I try to use just CSS to get as most of the initial look as possible.

7. August 30, 2005 11:55 PM

Quote this comment

what? Posted…

This is nuts, you can call more than one function from the onload event to begin with, and to second with, building various functions to perform various tasks and then calling the functions you want from a 'management' script is the ultimate in script independence, and thirdly the only thing you should be calling from body onload is functionality that needs to be performed after hte page is loaded. Almost every other object of the page has an onload event that can be used to call functionality as the page loads.

8. August 31, 2005 02:24 PM

Quote this comment

Drew Marsh Posted…

Since when can you only attach one handler to window onload? 1995 maybe, but these days any DOM Level 2 event can have multiple handlers attached to it. In DOM Level 2 Events[1] compliant browser you use addEventListener and in IE5+ you use attachEvent.

HTH, Drew

[1] http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/

9. September 6, 2005 12:03 PM

Quote this comment

Sam-I-Am Posted…

When you are ajax-ifying your page/app, this problem becomes more pressing. You need an event to fire to do your unobtrusive javascript / behavior layer enhancements. But if you are deferring load of any content (e.g. a tabset where tab content is only loaded when the user clicks on that tab), you need re-initiate the same cycle - watching for the "load" event, and re-applying behavior.

I've also run into problems with script inter-dependancies - where one library uses functions defined in another - so you need to either paste them all into one big library.js, or load them sequentially to avoid script errors and undefined objects. This all points at the need for a dependancy manager of sorts, where you can register tests or criteria (e.g to test for a "class", or dom node, or some flag) that need to be true before the associated functions are called. I've got something like this working and would be interested in comparing notes with anyone who is tackling the same problem.

10. September 6, 2005 12:34 PM

Quote this comment

Craig Posted…

Re: what?'s comment: "Almost every other object of the page has an onload event that can be used to call functionality as the page loads."

As far as I can tell, for elements, onload is only valid on BODY and frames in HTML 4.01, no other element. It may work on others (for example, IMG and LINK in IE), but it's probably wise not to rely on that functionality.

11. September 13, 2005 02:25 AM

Quote this comment

tvst Posted…

you can solve problem 2 like this (on the newest versions of IE, Safari/Konqueror, Mozilla)

if (document.getElementsByTagName && !window.ParseCtl)
{
	var ParseCtl =
	{
		onparse : function () {;},
		complete : false,
		callOnParse : function () 
		{
			if (document.getElementsByTagName("body").length == 0 || ParseCtl.complete) return;
			
			ParseCtl.complete = true; 
			ParseCtl.onparse();
		}
	};

	if (document.readyState) document.onreadystatechange = ParseCtl.callOnParse;
	else document.addEventListener("DOMContentLoaded", ParseCtl.callOnParse, null);	
};
That's what I do on the MP3Alizer and on the new version of imageswitcher.

12. September 13, 2005 01:14 PM

Quote this comment

tvs Posted…

actually, i posted the old code, which doesn't work with khtml. the new one is in this script, under the following comment:

// "onparse" setup code

13. September 18, 2005 11:43 PM

Quote this comment

Jehiah Posted…

Another way to attach event handlers while preserving the existing function call is as follows.

var old_window_onload = (window.onload) ? window.onload :  function(){};
window.onload = function(){old_window_onload();new_function();}

14. September 28, 2005 06:39 AM

Quote this comment

Bobby van der Sluis Posted…

I just published Using dynamic CSS to hide content before page load, which offers an updated version of the technique discussed in Unobtrusive show/hide behavior reloaded

15. October 7, 2005 08:16 AM

Quote this comment

Alex Tingle Posted…

My solution works like Jehiah's, but it's a bit more elegant and allows more than two functions to be chained together: function WindowOnload()

16. October 8, 2005 11:52 PM

Quote this comment

George Posted…

uhm can I ask someone codes for page counter to count the number of pages loaded 5 times? By using the onload event thanks!

17. November 23, 2005 07:36 PM

Quote this comment

Ashley Bowers Posted…

Thanks Jehiah for the extra way to attach event handlers while preserving the existing functions!!

18. May 10, 2006 12:51 PM

Quote this comment

Tanny O'Haley Posted…

The problem I found with timers is that the onload event can actually fire before your timer function. To get around this you should use both a timer function and onload with a variable to tell you that you've already run your function. You can use arguments.callee.done within your function to test if you've already run the function.

You might want to try out my DOMContentLoaded event for browsers. I created a DOMContentLoaded event function array and a DOMContentLoadedInit function that processes the array. To run the DOMContentLoadedInit function I use the DOMContentLoaded event for Mozilla browsers, the defer script attribute that Dean Edwards found for IE and a scheduler function (with code from Brothercake and Simon Willison) in conjuction with onload for all other browsers. You can find this at DOMContentLoaded Event for Browsers.