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の準備ができました!」(すべてのスクリプトが実行されました)が表示されます。
この規則には2つの例外があります。
- 後ほど説明する
async
属性を持つスクリプト(少し後ほど)は、DOMContentLoaded
をブロックしません。 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] 初期 readyState:loading
- [2] readyState:interactive
- [2] DOMContentLoaded
- [3] iframe onload
- [4] img onload
- [4] readyState:complete
- [4] window onload
角括弧内の数字は、発生したおおよその時間を示しています。同じ数字でラベル付けされたイベントはおおよそ同時に発生します(±数ミリ秒)。
document.readyState
は、DOMContentLoaded
の直前にinteractive
になります。これら2つのことは実際には同じ意味です。document.readyState
は、すべてのリソース(iframe
とimg
)が読み込まれるとcomplete
になります。ここでは、それがimg.onload
(img
は最後のリソース)とwindow.onload
とほぼ同時に発生していることがわかります。complete
状態への切り替えは、window.onload
と同じ意味です。違いは、window.onload
は常に他のすべてのload
ハンドラの後で行われることです。
概要
ページ読み込みイベント
DOMContentLoaded
イベントは、DOMの準備が整ったときにdocument
でトリガーされます。この段階でJavaScriptを要素に適用できます。<script>...</script>
や<script src="..."></script>
などのスクリプトはDOMContentLoadedをブロックし、ブラウザはその実行を待ちます。- 画像やその他のリソースも読み込みが継続される場合があります。
window
のload
イベントは、ページとすべてのリソースが読み込まれたときにトリガーされます。それほど長く待つ必要がないため、めったに使用されません。window
のbeforeunload
イベントは、ユーザーがページを離れようとしたときにトリガーされます。イベントを取り消すと、ブラウザはユーザーが本当に離れたいかどうかを確認します(たとえば、保存されていない変更がある場合)。window
オブジェクトのunload
イベントは、ユーザーが最終的にページを離れる際にトリガーされます。ハンドラー内では、遅延を伴う処理やユーザーへの問い合わせを行うことはできません。この制限のため、このイベントはめったに使用されません。navigator.sendBeacon
を使用してネットワークリクエストを送信できます。document.readyState
はドキュメントの現在の状態を表し、その変化はreadystatechange
イベントで追跡できます。loading
– ドキュメントの読み込み中。interactive
– ドキュメントの解析が完了。DOMContentLoaded
イベントとほぼ同時に発生しますが、それよりも前です。complete
– ドキュメントとリソースの読み込みが完了。window.onload
イベントとほぼ同時に発生しますが、それよりも前です。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合は、サンドボックス(plnkr、jsbin、codepen…)を使用してください。