onDOMReady: No Browser Sniffing!
January 12, 2009
For the most part, I tend to rely on window.onload; its simple, cross-browser, and the alternative onDOMReady functions were unreliable because of the use of browser sniffing. Aside from that, a low byte size in your pages is not only relatively easy to attain, but it should also be kept in mind during development – bandwidth can be a valuable thing. Thus, images shouldn’t result in a long load time and therefore should eliminate the very requirement of an onDOMReady related function.
Despite the debate over the very need of such a function, I decided to pursue it anyways out of pure curiosity. Once I committed to it and began coding I was fully expecting something to go wrong, after all, if all the major libraries couldn’t figure it out then there must not be a solution that doesn’t resort to browser detection, right? Unless of course they never even tried. To my surprise it was fairly simple, within 20 minutes I had a working version that was every bit as reliable as those found in YUI, Prototype, Mootools, etc. except no browser sniffing. Surely these talented developers (and I don’t doubt their skill) could have found a similar solution had they looked. The only exception to this is jQuery who I am very pleased to see have made great steps in completely removing browser sniffing with jQuery 1.3, including in their use of the ready function for onload handling, credit to John Resig and the jQuery team. Regardless, I present to you in just 42 lines of code, a simple function to handle all your onload requirements with no browser sniffing!
Features
The function itself works similar to the other solutions available around the web including those found in the various Javascript libraries. However, rather than separating each routine via browser detection, I simply use one function to parse the document’s current state and determine if the DOM is ready for manipulation. For Firefox and Opera a simple check of event type will determine if it is DOMContentLoaded. Safari and IE will check against the document’s ready state, and alternatively IE will routinely try to scroll the document left, if successful – the DOM is ready. Finally in case all else fails, the onload event will bring up the rear. Upon completion, the supplied function is called and all event listeners are removed and nulled out to prevent memory leaks. Test it out here.
function onDOMReady(fn, ctx){ var ready, timer; var onChange = function(e){ if(e && e.type == "DOMContentLoaded"){ fireDOMReady(); }else if(e && e.type == "load"){ fireDOMReady(); }else if(document.readyState){ if((/loaded|complete/).test(document.readyState)){ fireDOMReady(); }else if(!!document.documentElement.doScroll){ try{ ready || document.documentElement.doScroll('left'); }catch(e){ return; } fireDOMReady(); } } }; var fireDOMReady = function(){ if(!ready){ ready = true; fn.call(ctx || window); if(document.removeEventListener) document.removeEventListener("DOMContentLoaded", onChange, false); document.onreadystatechange = null; window.onload = null; clearInterval(timer); timer = null; } }; if(document.addEventListener) document.addEventListener("DOMContentLoaded", onChange, false); document.onreadystatechange = onChange; timer = setInterval(onChange, 5); window.onload = onChange; };
I also made a similar version that utilized a defer script for IE and worked just as well, however due to a fundamental flaw in the implementation I recently became aware of, I decided the current version as it is now would suffice. Furthermore, the method could be modified to accept multiple functions and simply append them to an array which could then be iterated over and executed accordingly once the DOM is loaded, facilitating multiple onload handlers. Depending on the response I may add those or other features at some later point in time, or feel free to modify it as you see fit for your own personal use.
This seems like a great idea. I’m surprised nobody else has commented, as this seems to be one of the few standalone solutions available. I’m wondering, is this still the current code you’re using, or has it been tweaked? Any problems with older browsers?
@Leo
The code is still the same but to be honest, I haven’t looked at it in a while, so perhaps some tweaking is in order. Although I had initially created an alternate version which employed a deferred script instead of Diego Perini’s doScroll hack for IE.
Now that I think about it, I had planned on adding the ability to call the function multiple times with multiple callbacks rather than just once.
As per browser support, I have tested the method to success in IE6+, FF2+, Opera 9+, Safari 2+, and Chrome 1+ but haven’t been able to get my hands on any older browsers so I can’t be too sure. However, the function should degrade gracefully to window.onload in any browsers that do not support the more advanced methods the script utilizes thanks to the lack of browser detection.
Looks like this could be a nice little function. Sounds rather like Dean Edwards’ method (see for instance http://peter.michaux.ca/articles/the-window-onload-problem-still), but I do like the idea of mopping up all the event listeners at the end. One question though, and probably because I just don’t understand it properly, but how does this function fire? I ask because all the event handlers and calls to fire the function actually reside within a function definition, there seems to be no trigger outside this? I’ve tried running your (fully documented) version myself, and things will only fire if, say onload, is placed outside the function (ie globally). What am I missing?
@Julie
If I am understanding your question correctly, the function is able to fire because it is still within the context of the onDOMReady function, any function definitions (fireDOMReady) that reside within that context are part of the scope chain and therefore have access to all variables at any level equal to or higher than its own. The event handlers hold a reference to fireDOMReady which in turn holds a reference to the user-provided function.
I am not to sure what your eluding to with this; “things will only fire if, say onload, is placed outside the function (ie globally).” But I can tell you, that onDOMReady can be called within any context, but it is important to note it can only be called once per page (I hope to upgrade this ability in the future to unlimited calls). It is typically best to pass the function directly to the method rather than a reference to avoid polluting the global namespace:
onDOMReady(function(){
//do something
})
I hope this answers your question!
Hi Ryan,
Many thanks for getting back so quickly, and yep you’ve sure answered my question! I hadn’t taken it in properly that you’re passing a function into the onDOMReady. I’ll have to delve into this in more detail, it’s a bit of a revelation to me, but sounds a great technique to keep things out of the global context. Sounds like you’ve really got something here
)