There are cases when you need to show your app as soon as possible without waiting for the page to be completely loaded. It’s a common v for embeddable apps which are injected into a 3rd party web page via asynchronous script
tag:
<script async src="//my.cool.widget"></script>
And that’s the case when window.onload is not enough because as the doc says
At this point, all of the objects in the document are in the DOM, and all the images, scripts, links and sub-frames have finished loading.
It simply may be too late for your app and the user may scroll away from it when onload happens.
Fortunately, there is DOMContentLoaded event that is fired once the initial HTML doc has been loaded and parsed. That’s a perfect time to start loading your app:
window.addEventListener("DOMContentLoaded", function () {
console.log('DOM is loaded');
runTheApp();
});
This code should work fine in all major modern browsers. That’s great but there a pitfall. If your listener is added after DOMContentLoaded
is fired, your app will not start.
To solve this problem, you need to use document.readyState
which indicates the progress of loading the document.
If the state is complete
, just start the app - all HTML is there and all resources have been loaded.
If the state is interactive
, it means that the user can interact with the page at that point in time but the page is still loading. The following code handles the case when DOMContentLoaded
was fired:
if (document.readyState === "complete"
|| document.readyState === "loaded"
|| document.readyState === "interactive") {
runTheApp()
} else {
window.addEventListener("DOMContentLoaded", function () {
console.log('DOM is loaded');
runTheApp();
});
}
That’s great code but my testing shows that there is another pitfall. If you need to query the DOM of the web page, you may not rely on readyState
and be sure the needed HTML is available. In my case, the first thing the app did was calling document.querySelectorAll
to find some elements on the web page. The testing showed that if the readyState is interactive
the elements may or may not be there.
Therefore, I decided to add the following hack:
if (document.readyState === "complete"
|| document.readyState === "loaded"
|| document.readyState === "interactive") {
var elements = document.querySelectorAll('.someSelector');
if (elements.length > 0) {
runTheApp(elements)
} else {
setTimeout(function() {
var elements = document.querySelectorAll('.someSelector');
runTheApp(elements);
}, 50); // giving the page some time, maybe it will load
}
} else {
window.addEventListener("DOMContentLoaded", function () {
console.log('DOM is loaded');
runTheApp();
});
}
This code checks if elements that are needed for the app are present on the page and schedules a second check after 50ms if it fails to find the elements hoping that they will appear. Of course, that’s very hacky solution and may not always work. But my testing showed me that it’s good enough for my app.
Thanks for reading and I would be thankful for any suggestions of better methods.