90

I'm writing a Javascript script. This script will probably be loaded asynchronously (AMD format).

In this script, I'd like to do nothing important until the window.load event was fired. So I listen to the window "load" event.

But if the script is loaded after window.load event... how can I know window.load was already fired?

And of course I don't want to add something in any other scripts (they are all loaded async, the problem is the same) :)

Edit :

Imagine an HTML doc with no Javascript in it at all.

Than someone insert in this doc a tag, and this script tag loads my Javascript file.

This will execute my script.

How this script can know if window.load was already fired ?

No jQuery, not any script in the HTML doc before mine.

Is it possible to know ??

I found the window.document.readystate property. This property is for document "ready" event I guess, not for window "load". Is there anything similar for window "load" event ?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Nicolas
  • 1,639
  • 1
  • 14
  • 12
  • Why not use jQuery document ready event? Otherwise, hook window.load event and set a flag (e.g. `window.loadFired`), and queue timeout to check the flag every 50ms. – Nikola Radosavljević Nov 13 '12 at 16:30
  • Can you just set a global boolean in a window load event that is not loaded asynchronously? – Xitalogy Nov 13 '12 at 16:31
  • 2
    I want to wait window "load" event because before this event, the browser is still busy. I want to start working when the browser is really ready.... Maybe this is stupid, but anyway it's a problem not to be able to know if an event was fired or not... isn't it ? – Nicolas Nov 13 '12 at 17:09
  • If there are no scripts that rely on the load event, what difference does it make whether you detect it or not? Perhaps what you really need to know is if the document has loaded all its elements, which the document.readyState would seem to do @Nicolas – Xitalogy Nov 13 '12 at 17:39
  • document "ready" means the DOM is ready (and any blocking elements like CSS are loaded too). But what about images for example ? window load is the one telling everything is really loaded (except all async loaded elements of course). That's why I'd like to detect this event. But I will probably have to change my mind :p – Nicolas Nov 13 '12 at 17:43
  • 3
    @nicolas document.readyState appears contain the string "complete" after all the elements of the document have been loaded, if this is accurate and my understanding is correct: https://developer.mozilla.org/en-US/docs/DOM/document.readyState This MSDN article also seems to indicate it might be what you need: http://msdn.microsoft.com/en-us/library/ie/ms534359(v=vs.85).aspx – Xitalogy Nov 13 '12 at 22:23
  • I am having the dilema when working with a bookmarklet. – gcb Oct 18 '13 at 20:46

6 Answers6

70

The easiest solution might be checking for document.readyState == 'complete', see http://www.w3schools.com/jsref/prop_doc_readystate.asp

Matthias Samsel
  • 937
  • 1
  • 8
  • 14
  • 13
    Same solution from MDN https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState#Values – lizlux Jul 30 '15 at 18:33
  • 5
    According to MDN, document.readyState becomes 'complete' when the load event is -about- to fire, so there is a possibility of a race condition where your code will run before the load event. If you absolutely need your code to run after the load event (but don't know whether the load event has fired), you may be out of luck. – Ted Phillips Aug 12 '18 at 20:55
  • @Ted Phillips: maybe then would be best to set a small timeout, so the testing would happen in the next event loop – Panu Logic Mar 21 '22 at 00:27
49

Quick Answer

To quickly answer the question's title:

document.readyState === 'complete'

Deeper Example

Below is a nice helper if you want to call code upon a window load, while still handling the case where the window may have already loaded by the time your code runs.

function winLoad(callback) {
  if (document.readyState === 'complete') {
    callback();
  } else {
    window.addEventListener("load", callback);
  }
}

winLoad(function() {
  console.log('Window is loaded');
});

Note: code snippets on here actually don't run in the same window context so document.readyState === 'complete' actually evaluates to false when you run this. If you put the same into your console right now for this window it should evaluate as true.

See also: What is the non-jQuery equivalent of '$(document).ready()'?


Handling the Edge Case from @IgorBykov via Comments

Igor brought up an interesting issue in the comments, which the following code can try to handle given a best-effort-timeout.

The problem is that the document.readyState can be complete before the load event fires. I'm not certain what potential problems this may cause.

Some Documentation About the Flow and Event Firing

Complete: The state indicates that the load event is about to fire.

Gives a live example of event firing ie:

  1. readyState: interactive
  2. Event Fired: DOMContentLoaded
  3. readyState: complete
  4. Event Fired: load

There's a brief moment where the readyState may be complete before load fires. I'm not sure what issues you may run into during this period.

The below code registers the load event listener, and sets a timeout to check the readyState. By default it will wait 200ms before checking the readyState. If the load event fires before the timeout we make sure to prevent firing the callback again. If we get to the end of the timeout and load wasn't fired we check the readyState and make sure to avoid a case where the load event could potentially still fire at a later time.

Depending on what you're trying to accomplish you may want to run the load callback no matter what (remove the if (!called) { check). In your callback you might want to wrap potential code in a try/catch statement or check for something that you can gate the execution on so that when it calls twice it only performs the work when everything is available that you expect.

function winLoad(callback, timeout = 200) {
  let called = false;

  window.addEventListener("load", () => {
    if (!called) {
      called = true;
      callback();
    }
  });

  setTimeout(() => {
    if (!called && document.readyState === 'complete') {
      called = true;
      callback();
    }
  }, timeout);
}

winLoad(function() {
  console.log('Window is loaded');
});
CTS_AE
  • 12,987
  • 8
  • 62
  • 63
  • Surprisingly enough this is not entirely true. The problem is that `document.readyState` might be `complete` meanwhile `load` wasn't fired yet. I was fighting with a bug in my project based on exactly this fact. This will surely work for any situation when you know that you'll be either too early or too late for the party, which is to say, in 95% of cases. In the resting 5% you might probably want to defer the `callback` invocation inside the `if` block with either `setTimeout`, a `Promise` or `requestIdleCallbck`. – Igor Bykov May 24 '22 at 22:10
  • @IgorBykov That's why there's the else block to add an event listener for `load` otherwise, unless you're talking about a different scenario that I'm misunderstanding. Ah, you're saying the load event hasn't happened yet. After reading: https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState it states: `Complete: The state indicates that the load event is about to fire.` – CTS_AE May 24 '22 at 22:44
  • This live example shows the firing order too: https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event. Maybe it's sufficient enough to just add the load event listener no matter what, but then you need to make sure it doesn't fire twice. I think there's a way to do this without any timeouts. I'll add a round 2 and have a go at it, but I'm also interested in some of the options you mentioned if you would like to provide an answer showing them as an example. I thought it was just a bad browser implementation at first, but it still seems like `complete` should be sufficient;; dang. – CTS_AE May 24 '22 at 22:45
  • Hey! I believe, you can never be sure (using older APIs) at least. Theoretically, it should be extremely complex to get into the gap between `complete` & `load`, but in practice - it's perfectly possible! :) – Igor Bykov May 24 '22 at 23:43
  • Unconditional `load` listener doesn't solve the problem either, unfortunately since there's a perfect opportunity that it'll never fire at all (because you've attached it too late, for instance). The polling isn't an option either since you can't poll the `load` status itself. – Igor Bykov May 24 '22 at 23:49
  • Probably, a good option would be to unconditionally attach the `load` listener, wait for some time (e.g. 200 ms) & if the listener wasn't executed yet & `readyState` is `complete`, we know that we've come well after `load`. That'd be a dirty but relatively reliable way. – Igor Bykov May 24 '22 at 23:52
  • @IgorBykov I added some code that would hopefully handle your edge case. I'm still not certain what breaks for you in your state between `readyState: complete` and the `load` event firing. I also mention how you may want to potentially call the callback multiple times with a slight code change. I'm sure you've already handled your wonky case, but I figured I would add some code to help handle it if others run across your problem as well. I'm not sure if you're not just finding a dynamic element that doesn't yet exist until later after a complete & load potentially either? – CTS_AE May 25 '22 at 20:30
  • Hey, thank you! In my case I needed to `postMessage` to an iframe once it's ready to receive it. The problem was that if I did so before `load` (even with `readyState === 'complete'`) the receiving code wasn't yet listening for the message. In my particular case, the polling was possible (since the recieveng end can respond and confirm that the message was received), and that's exactly what I ended up doing (which, in a way, is similar to your solution) anyway - thank you for adjusting the answer :) – Igor Bykov May 26 '22 at 06:26
14

Browser navigation performance loadEventEnd metric can be used to determinate if load event was triggered:

let navData = window.performance.getEntriesByType("navigation");
if (navData.length > 0 && navData[0].loadEventEnd > 0)
{
    console.log('Document is loaded');
} else {
    console.log('Document is not loaded');
}
Evgeny
  • 274
  • 3
  • 8
  • 7
    Simplified: `if (performance.timing.loadEventEnd) { /* window loaded */ }` – Blaise Apr 24 '19 at 20:19
  • 6
    `performance.timing` is being deprecated, so while it might be simpler it is possibly dangerous to use. – MynockSpit Feb 17 '20 at 21:34
  • 5
    `performance.getEntriesByType("navigation").every((e) => e.loadEventEnd) // true|false` for a non deprecated version – Blaise May 14 '20 at 13:47
0

Based on @CTS_AE's approach, I have put together a solution for envrionments where:

  • window.addEventListener('load', activateMyFunction); and
  • window.addEventListener('DOMContentLoaded', activateMyFunction);

don't work.

It requires a single character substitution (eg. from

window.addEventListener('load', activateMyFunction);

to

window_addEventListener('load', activateMyFunction);)

The function window_addEventListener() looks like this:

const window_addEventListener = (eventName, callback, useCapture = false) => {

  if ((document.readyState === 'interactive') || (document.readyState === 'complete'))   {

    callback();
  }
}
Rounin
  • 27,134
  • 9
  • 83
  • 108
-1

If you don't want to use jQuery, the logic it uses is:

if( !document.body )
    setTimeout( checkAgain, 1 );

So between the windows loaded event and checking if the body property of the document is available, you can check if the DOM is ready

Matt R. Wilson
  • 7,268
  • 5
  • 32
  • 48
  • 3
    document.body is available when DOM is ready. not when window "load" is triggered. This solution is for detecting document "ready" (DOM ready), but I'd prefere to guess window "load" event... – Nicolas Nov 13 '12 at 17:14
  • 1
    One should try to avoid `setTimeout` polling where possible, especially when eventing for exists for it. ie: `window.addEventListener("load", callback)` https://developer.mozilla.org/en-US/docs/Web/Events/load – CTS_AE Jan 14 '19 at 10:12
-4

what about overriding window.load?

window._load = window.load;
window.load = function(){
  window.loaded = true;
  window._load();
}

Then check for

if (window.loaded != undefined && window.loaded == true){
    //do stuff
}
Eric Frick
  • 847
  • 1
  • 7
  • 18
  • 2
    This will not do the job. If window "load event was already fired, overriding window.load is useless, it has already been executed, it won't be anymore... – Nicolas Nov 13 '12 at 17:18