78

I'm trying to help developing a library and for it I'm trying to work with page loading.
In the process I want to make the library completely compatible with the use of defer and async.

What I want is simple:
How can I know that DOMContentLoaded was fired by the time the file is executed?

Why is this so difficult?
In IE, document.readyState show interactive before DOMContentLoaded.
I won't use browser detection in any way, it's against the policy of me and the rest of the participants.

What's the correct alternative?

Edit:

Seems like I wasn't clear enough. I'm not interested to know if the load event has already occurred!!! I already knew how to solve that problem! I want to know how to solve with DOMContentLoaded!!!

brunoais
  • 6,258
  • 8
  • 39
  • 59
  • Set a listener that sets a property or variable. If it's set, the event has been dispatched. Of course you might be in a browser that doesn't support the event, in which case it will never occur. – RobG Feb 26 '12 at 22:42
  • @RobG I already had the answer for the browsers the ado not support DOMContentLoaded. For those, I use the load event and the onload event. I still don't have the answer for the question I made – brunoais Feb 27 '12 at 06:22
  • There is no "onload" event, there is an *onload* attribute/property for setting listeners for the *load* event. If you want to know unequivocally if DOMContentLoaded has occurred, set a listener and see if it's been called. – RobG Feb 27 '12 at 12:01

6 Answers6

81

For seeing if all resources in the page have been loaded:

if (document.readyState === "complete" || document.readyState === "loaded") {
     // document is already ready to go
}

This has been supported in IE and webkit for a long time. It was added to Firefox in 3.6. Here's the spec. "loaded" is for older Safari browsers.

If you want to know when the page has been loaded and parsed, but all subresources have not yet been loaded (which is more akin to DOMContentLoaded), you can add the "interactive" value:

if (document.readyState === "complete" 
     || document.readyState === "loaded" 
     || document.readyState === "interactive") {
     // document has at least been parsed
}

Beyond this, if you really just want to know when DOMContentLoaded has fired, then you'll have to install an event handler for that (before it fires) and set a flag when it fires.

This MDN documentation is also a really good read about understanding more about the DOM states.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 4
    You are not answering the question. I want to know about DOMContentLoaded event, not load event. The load event is easy, the DOMContentLoaded I don't know. – brunoais Feb 27 '12 at 06:20
  • 2
    @brunoais - then install a handler for the DOMContentLoaded event and set a flag when it fires. I don't think there's another option. – jfriend00 Feb 27 '12 at 15:25
  • 1
    If you want to include when the document has been parsed, but sub-resources are still loading, you can check for "interactive" too. See the second example I added to my answer. – jfriend00 Feb 27 '12 at 15:37
  • just adding spice to this discussion http://ablogaboutcode.com/2011/06/14/how-javascript-loading-works-domcontentloaded-and-onload/ – Jayapal Chandran Sep 09 '13 at 09:56
  • 5
    The first code snippet is incorrect - `document.readyState == "interactive"` can also indicate that DOMContentLoaded was fired. See here: https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState – thedayturns Sep 19 '15 at 00:44
  • @jfriend00 it doesn't work for google docs, any idea why? – JinSnow Dec 12 '21 at 09:45
  • @JinSnow - Please write your own question and show your specific code and your specific situation. – jfriend00 Dec 12 '21 at 15:10
  • I did it here (it uses your code) https://stackoverflow.com/questions/70322561/userscript-how-to-check-in-javascript-when-a-google-doc-is-loaded – JinSnow Dec 12 '21 at 17:01
25

You can check the document's readyState value and this way tell if the event was fired or not. Here's the code to run a function named start() when the document has finished parsing:

if (/complete|interactive|loaded/.test(document.readyState)) {
    // In case the document has finished parsing, document's readyState will
    // be one of "complete", "interactive" or (non-standard) "loaded".
    start();
} else {
    // The document is not ready yet, so wait for the DOMContentLoaded event
    document.addEventListener('DOMContentLoaded', start, false);
}

Notice that the code above detects when the document has finished parsing. Beware that's not the same as detecting if DOMContentLoaded was fired (which happens immediately after interactive), but it serves the same practical purpose, i.e., it tells you that the document has finished loading and has been parsed, but sub-resources such as images, stylesheets and frames are still loading (source).

oriadam
  • 7,747
  • 2
  • 50
  • 48
  • 2
    This is not correct. `DOMContentLoaded` is fired only *after* `interactive` (proof here: https://jsfiddle.net/luciopaiva/grs82byv/20/) – Lucio Paiva Dec 06 '19 at 20:51
  • @LucioPaiva thanks and you're right - but it only mean that the comment about DCL already fired is incorrect. the code still works perfectly in all cases, so why -1? – oriadam Dec 08 '19 at 08:39
  • 1
    You are right, I was misguided by the comment there. I edited your answer fixing it and adding a bit more info; please let me know what you think. – Lucio Paiva Dec 08 '19 at 11:56
  • 1
    *“A more readable regexp would be […]”* → Given the choice, you should always prefer the more readable version of the same code. – Denilson Sá Maia Jan 27 '20 at 13:26
  • @DenilsonSáMaia i agree. changed it. – oriadam Jan 29 '20 at 14:25
  • @oriadam Any idea why it doesn't work for google docs? – JinSnow Dec 12 '21 at 09:52
17

How to correctly run something on DOMContentLoaded (or ASAP)

If you need to wait for the HTML document to be fully loaded and parsed to run something, you need to wait for DOMContentLoaded, no doubt about it. But if you can't control when your script is going to execute, it's possible that DOMContentLoaded was already fired by the time you get a chance to listen for the event.

To take that into consideration, your code needs to check if DOMContentLoaded was already fired and, if so, proceed to executing right away whatever it is that needed to wait for DOMContentLoaded:

function runWhenPageIsFullyParsed() {
    // your logic goes here
}

if (document.readyState === "complete") {
    // already fired, so run logic right away
    runWhenPageIsFullyParsed();
} else {
    // not fired yet, so let's listen for the event
    window.addEventListener("DOMContentLoaded", runWhenPageIsFullyParsed);
}

The correct order of events is:

  • document.readyState starts with loading
  • document.readyState changes to interactive
  • window's DOMContentLoaded event gets fired
  • document.readyState changes to complete
  • window's load event gets fired load

Reference: MDN

You can check the complete order of events during page loading in this fiddle.

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
  • 2
    Adding `log(document.readyState);` at the beginning of the JS in your fiddle helps to point out the `loading` state of document.readyState which occurs before interactive. I would add that important step to the order of events. Outlined on this [MDN readyState](https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState) article. Thanks! – King Holly May 28 '20 at 04:35
  • I tried the above solution and there was still few times where my code won't run. So I ended up adding ```document.readyState === "interactive"``` condition to my code. – Saurabh Gupta May 11 '23 at 08:31
  • 1
    @KingHolly I've updated the answer with your suggestion, thanks. – Lucio Paiva May 12 '23 at 09:27
  • @SaurabhGupta that is interesting. Can you show some code that reproduces the problem? I tried re-running the code in my fiddle above, but it works every time. – Lucio Paiva May 12 '23 at 09:27
  • @LucioPaiva I am running it inside ```$.ajax.done``` method. Also, this code in running in an OSGI container where my code is installed into it (Atlassian plugin). So the load order is not controlled by me and there are lot of different items which are being loaded at the same time. – Saurabh Gupta May 13 '23 at 07:18
1

Try this or look at this link

<script>
    function addListener(obj, eventName, listener) { //function to add event
        if (obj.addEventListener) {
            obj.addEventListener(eventName, listener, false);
        } else {
            obj.attachEvent("on" + eventName, listener);
        }
    }

    addListener(document, "DOMContentLoaded", finishedDCL); //add event DOMContentLoaded

    function finishedDCL() {
        alert("Now DOMContentLoaded is complete!");
    }
</script>

Note

If you have a <script> after a <link rel="stylesheet" ...>

the page will not finish parsing - and DOMContentLoaded will not fire - until the stylesheet is loaded

OammieR
  • 2,800
  • 5
  • 30
  • 51
  • 2
    Nice try but that does not go after what I'm really after. That does not check if the event had already been fired, that only checks if the event is being fired. I know how to check if the event is being fired but I don't know how to check if the event had already been fired – brunoais Feb 27 '12 at 13:54
0

If you want to know "exactly" if DOMContentLoaded was fired, you could use a boolean flag like this:

var loaded=false;
document.addEventListener('DOMContentLoaded',function(){
loaded=true;
...
}

then check with:

if(loaded) ...
deviato
  • 2,027
  • 1
  • 15
  • 9
  • 1
    Nice try :). That is not really an answer, though. As I want it to work correctly with all (major) browsers and with async and defer, I can't be so sure that that will work as expected due to IE. – brunoais Mar 21 '14 at 08:50
  • 4
    IE is not a browser :D – deviato Jul 11 '14 at 08:25
-8

Here is the thing, if some other library (such as jQuery) already used DOMContentLoaded, you can't use it again. That can be bad news, because you end up without being able to use it. You are gonna say, why not just use $(document).ready(), because, NO!, not everything needs to use jQuery, specially if it is a different library.

Kevin Kopf
  • 13,327
  • 14
  • 49
  • 66
  • 3
    It's not true that you can not have multiple event listeners. Please read up on https://developer.mozilla.org/en-US/docs/DOM/document.addEventListener and the Publish/Subscribe (pub sub) pattern: https://msdn.microsoft.com/en-us/library/ff649664.aspx – dotnetCarpenter Nov 04 '16 at 17:02