2022年10月14日

ページ:DOMContentLoaded、load、beforeunload、unload

HTMLページのライフサイクルには、3つの重要なイベントがあります。

  • DOMContentLoaded – ブラウザがHTMLを完全に読み込み、DOMツリーが構築されましたが、画像<img>やスタイルシートなどの外部リソースはまだ読み込まれていない可能性があります。
  • load – HTMLだけでなく、画像やスタイルなどすべての外部リソースも読み込まれました。
  • beforeunload/unload – ユーザーがページを離れています。

各イベントは有用な場合があります。

  • DOMContentLoadedイベント – DOMの準備が整ったので、ハンドラはDOMノードを検索し、インターフェースを初期化できます。
  • loadイベント – 外部リソースが読み込まれたので、スタイルが適用され、画像サイズがわかるようになります。
  • beforeunloadイベント – ユーザーがページを離れようとしています。ユーザーが変更を保存したかどうかを確認し、本当に離れたいかどうかを確認できます。
  • unload – ユーザーはほぼ離れましたが、統計の送信など、いくつかの操作を開始できます。

これらのイベントの詳細を調べてみましょう。

DOMContentLoaded

DOMContentLoadedイベントは、documentオブジェクトで発生します。

これをキャッチするには、addEventListenerを使用する必要があります。

document.addEventListener("DOMContentLoaded", ready);
// not "document.onDOMContentLoaded = ..."

例えば

<script>
  function ready() {
    alert('DOM is ready');

    // image is not yet loaded (unless it was cached), so the size is 0x0
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  }

  document.addEventListener("DOMContentLoaded", ready);
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

この例では、DOMContentLoadedハンドラはドキュメントが読み込まれたときに実行されるため、下の<img>を含むすべての要素を確認できます。

しかし、画像の読み込みを待つことはありません。そのため、alertはゼロサイズを表示します。

一見、DOMContentLoadedイベントは非常にシンプルです。DOMツリーの準備が整いました—これがイベントです。しかし、いくつかの特殊性があります。

DOMContentLoadedとスクリプト

ブラウザがHTMLドキュメントを処理し、<script>タグに遭遇すると、DOMの構築を続行する前に実行する必要があります。これは、スクリプトがDOMを変更したり、document.writeで書き込んだりする可能性があるためであり、DOMContentLoadedは待つ必要があります。

そのため、DOMContentLoadedは必ずそのようなスクリプトの後で発生します。

<script>
  document.addEventListener("DOMContentLoaded", () => {
    alert("DOM ready!");
  });
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>

<script>
  alert("Library loaded, inline script executed");
</script>

上記の例では、最初に「ライブラリが読み込まれました...」、次に「DOMの準備ができました!」(すべてのスクリプトが実行されました)が表示されます。

DOMContentLoadedをブロックしないスクリプト

この規則には2つの例外があります。

  1. 後ほど説明するasync属性を持つスクリプト(少し後ほど)は、DOMContentLoadedをブロックしません。
  2. document.createElement('script')で動的に生成され、その後Webページに追加されたスクリプトも、このイベントをブロックしません。

DOMContentLoadedとスタイル

外部スタイルシートはDOMに影響を与えないため、DOMContentLoadedはそれらを待ちません。

しかし、落とし穴があります。スタイルの後にスクリプトがある場合、そのスクリプトはスタイルシートの読み込みを待つ必要があります。

<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // the script doesn't execute until the stylesheet is loaded
  alert(getComputedStyle(document.body).marginTop);
</script>

その理由は、スクリプトが上記の例のように、要素の座標やその他のスタイルに依存するプロパティを取得したい可能性があるためです。当然、スタイルの読み込みを待つ必要があります。

DOMContentLoadedはスクリプトを待機するため、その前にもスタイルを待機するようになりました。

ブラウザの組み込みオートフィル

Firefox、Chrome、Operaは、DOMContentLoadedでフォームを自動入力します。

たとえば、ページにログインとパスワードのフォームがあり、ブラウザが値を記憶している場合、DOMContentLoadedでそれらを自動入力しようとします(ユーザーによって承認されている場合)。

そのため、DOMContentLoadedが長時間読み込みのスクリプトによって延期されると、自動入力も待機します。一部のサイト(ブラウザの自動入力を使用する場合)では、ログイン/パスワードフィールドがすぐに自動入力されないものの、ページが完全に読み込まれるまで遅延があることに気づいたことがあるかもしれません。これは実際にはDOMContentLoadedイベントまでの遅延です。

window.onload

windowオブジェクトのloadイベントは、スタイル、画像、その他のリソースを含むページ全体が読み込まれたときにトリガーされます。このイベントはonloadプロパティを介して利用できます。

次の例では、window.onloadがすべての画像を待つため、画像サイズが正しく表示されます。

<script>
  window.onload = function() { // can also use window.addEventListener('load', (event) => {
    alert('Page loaded');

    // image is loaded at this time
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  };
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

window.onunload

訪問者がページを離れると、unloadイベントがwindowでトリガーされます。そこで、関連するポップアップウィンドウを閉じるなど、遅延を伴わない操作を実行できます。

注目すべき例外は、分析の送信です。

ページの使用方法に関するデータ(マウスクリック、スクロール、表示されたページ領域など)を収集するとします。

当然、unloadイベントはユーザーが私たちを離れるときであり、サーバーにデータを保存したいと考えています。

そのようなニーズには、仕様https://w3c.github.io/beacon/で説明されている特別なnavigator.sendBeacon(url, data)メソッドがあります。

バックグラウンドでデータを送信します。別のページへの遷移は遅延しません。ブラウザはページを離れますが、それでもsendBeaconを実行します。

使用方法を次に示します。

let analyticsData = { /* object with gathered data */ };

window.addEventListener("unload", function() {
  navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
});
  • リクエストはPOSTとして送信されます。
  • Fetchの章で説明されているように、文字列だけでなく、フォームやその他の形式も送信できますが、通常は文字列化されたオブジェクトです。
  • データは64kbに制限されています。

sendBeaconリクエストが完了すると、ブラウザはおそらくすでにドキュメントを離れているため、サーバー応答を取得する方法がありません(分析の場合は通常空です)。

一般的なネットワークリクエストのfetchメソッドでは、そのような「ページ離脱後」のリクエストを行うためのkeepaliveフラグもあります。Fetch APIの章で詳細情報を見つけることができます。

別のページへの遷移を取り消したい場合、ここでは実行できません。ただし、別のイベントであるonbeforeunloadを使用できます。

window.onbeforeunload

訪問者がページから離れるナビゲーションを開始した場合、またはウィンドウを閉じようとすると、beforeunloadハンドラは追加の確認を求めます。

イベントを取り消すと、ブラウザはユーザーに本当に離れたいかどうかを確認する可能性があります。

このコードを実行してからページをリロードすることで試すことができます。

window.onbeforeunload = function() {
  return false;
};

歴史的な理由から、空ではない文字列を返すこともイベントのキャンセルとしてカウントされます。以前はブラウザはそれをメッセージとして表示していましたが、最新の仕様によると、そうすべきではありません。

例を次に示します。

window.onbeforeunload = function() {
  return "There are unsaved changes. Leave now?";
};

この動作は変更されました。なぜなら、一部のWebマスターはこのイベントハンドラを悪用して、誤解を招くような迷惑なメッセージを表示していたからです。そのため、現在でも古いブラウザはそれをメッセージとして表示する可能性がありますが、それ以外には、ユーザーに表示されるメッセージをカスタマイズする方法はありません。

event.preventDefault()beforeunloadハンドラでは機能しません。

奇妙に聞こえるかもしれませんが、ほとんどのブラウザはevent.preventDefault()を無視します。

つまり、次のコードは機能しない可能性があります。

window.addEventListener("beforeunload", (event) => {
  // doesn't work, so this event handler doesn't do anything
  event.preventDefault();
});

代わりに、そのようなハンドラでは、上記のコードと同様の結果を得るために、event.returnValueを文字列に設定する必要があります。

window.addEventListener("beforeunload", (event) => {
  // works, same as returning from window.onbeforeunload
  event.returnValue = "There are unsaved changes. Leave now?";
});

readyState

ドキュメントが読み込まれた後にDOMContentLoadedハンドラを設定するとどうなりますか?

当然、実行されません。

ドキュメントの準備が整っているかどうかがわからない場合があります。DOMが読み込まれた場合、今すぐでも後ででも、関数が実行されるようにしたいと考えています。

document.readyStateプロパティは、現在の読み込み状態を示します。

3つの可能な値があります。

  • "loading" – ドキュメントを読み込み中です。
  • "interactive" – ドキュメントは完全に読み込まれました。
  • "complete" – ドキュメントは完全に読み込まれ、画像などのすべてのリソースも読み込まれました。

そのため、document.readyStateを確認し、準備が整っている場合はすぐにハンドラを設定するか、コードを実行できます。

このように

function work() { /*...*/ }

if (document.readyState == 'loading') {
  // still loading, wait for the event
  document.addEventListener('DOMContentLoaded', work);
} else {
  // DOM is ready!
  work();
}

状態が変更されるとトリガーされるreadystatechangeイベントもあり、このようにしてすべての状態を出力できます。

// current state
console.log(document.readyState);

// print state changes
document.addEventListener('readystatechange', () => console.log(document.readyState));

readystatechangeイベントは、ドキュメントの読み込み状態を追跡するための代替メカニズムであり、以前から存在していました。最近はめったに使用されません。

完全性を期すために、イベントの流れを見てみましょう。

ここに、<iframe><img>、およびイベントをログに記録するハンドラを含むドキュメントがあります。

<script>
  log('initial readyState:' + document.readyState);

  document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
  document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));

  window.onload = () => log('window onload');
</script>

<iframe src="iframe.html" onload="log('iframe onload')"></iframe>

<img src="https://en.js.cx/clipart/train.gif" id="img">
<script>
  img.onload = () => log('img onload');
</script>

動作する例はサンドボックスにあります

一般的な出力

  1. [1] 初期 readyState:loading
  2. [2] readyState:interactive
  3. [2] DOMContentLoaded
  4. [3] iframe onload
  5. [4] img onload
  6. [4] readyState:complete
  7. [4] window onload

角括弧内の数字は、発生したおおよその時間を示しています。同じ数字でラベル付けされたイベントはおおよそ同時に発生します(±数ミリ秒)。

  • document.readyStateは、DOMContentLoadedの直前にinteractiveになります。これら2つのことは実際には同じ意味です。
  • document.readyStateは、すべてのリソース(iframeimg)が読み込まれるとcompleteになります。ここでは、それがimg.onloadimgは最後のリソース)とwindow.onloadとほぼ同時に発生していることがわかります。complete状態への切り替えは、window.onloadと同じ意味です。違いは、window.onloadは常に他のすべてのloadハンドラの後で行われることです。

概要

ページ読み込みイベント

  • DOMContentLoadedイベントは、DOMの準備が整ったときにdocumentでトリガーされます。この段階でJavaScriptを要素に適用できます。
    • <script>...</script><script src="..."></script>などのスクリプトはDOMContentLoadedをブロックし、ブラウザはその実行を待ちます。
    • 画像やその他のリソースも読み込みが継続される場合があります。
  • windowloadイベントは、ページとすべてのリソースが読み込まれたときにトリガーされます。それほど長く待つ必要がないため、めったに使用されません。
  • windowbeforeunloadイベントは、ユーザーがページを離れようとしたときにトリガーされます。イベントを取り消すと、ブラウザはユーザーが本当に離れたいかどうかを確認します(たとえば、保存されていない変更がある場合)。
  • windowオブジェクトのunloadイベントは、ユーザーが最終的にページを離れる際にトリガーされます。ハンドラー内では、遅延を伴う処理やユーザーへの問い合わせを行うことはできません。この制限のため、このイベントはめったに使用されません。navigator.sendBeaconを使用してネットワークリクエストを送信できます。
  • document.readyStateはドキュメントの現在の状態を表し、その変化はreadystatechangeイベントで追跡できます。
    • loading – ドキュメントの読み込み中。
    • interactive – ドキュメントの解析が完了。DOMContentLoadedイベントとほぼ同時に発生しますが、それよりも前です。
    • complete – ドキュメントとリソースの読み込みが完了。window.onloadイベントとほぼ同時に発生しますが、それよりも前です。
チュートリアルマップ