えっ、HTMLにライフサイクルがあるの？


[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/19385612-0b5f-6610-adf2-038e38958f79.png)](https://leapcell.io/?lc_t=q_htmlcycle)

HTML のネイティブライフサイクル（Lifecycle）は、HTML に関連するイベントやブラウザがウェブページを読み込み、処理する際に経る各段階を指します。HTML 自体はマークアップ言語であり、JavaScript のようなライフサイクルフックを持っていませんが、HTML のライフサイクルイベントは、JavaScript と DOM（文書オブジェクトモデル）の相互作用によって実際に管理されています。

## HTML の解析（HTML Parsing）

ブラウザがウェブページを読み込むと、サーバーから HTML ファイルを受信し、解析を開始します。この段階でブラウザは DOM ツリー（Document Object Model）構造を作成し、HTML を操作可能な DOM オブジェクトに変換します。

厳密には、HTML の解析はページ読み込みプロセスの重要な一部ですが、従来の意味での「ライフサイクルイベント」には直接含まれません。なぜなら、これは JavaScript で検出または監視できるイベントではないからです。しかし、より広義の観点から見ると、HTML の解析はページライフサイクル全体で不可欠な部分であり、HTML のライフサイクルを議論する際には非常に重要です。

このプロセスはブラウザ内部で行われるため、開発者が直接この段階を監視することはできません。ただし、HTML 構造を最適化したり、ブロックするリソース（例：JS ファイル）の読み込みを減らすことで、解析速度を向上させることが可能です。

## 外部リソースの読み込み（Resource Loading）

ブラウザが HTML を解析する際、外部リソースに遭遇すると、そのリソースの種類、読み込み方法（同期または非同期）、優先度に応じて、ページの読み込みやレンダリングをどのように進めるかを決定します。この動作は、ページのレンダリング順序やユーザーがコンテンツを目にする時間に直接影響します。

リソースの種類ごとに読み込みの挙動が異なり、ページの解析とレンダリングへの影響も異なります。

1. **CSS の読み込み**：ブラウザが `<link>` タグを見つけると、CSS ファイルが完全に読み込まれ解析されるまでページのレンダリングを停止します。CSS はレンダリングをブロックするリソースと見なされます。なぜなら、CSS ファイルが読み込まれていないとページのレイアウトやスタイルが正しくレンダリングされないからです。
2. **JavaScript の読み込み**：デフォルトでは、ブラウザが `<script>` タグを見つけると、JavaScript ファイルの読み込みと実行が完了するまで HTML の解析を停止します。この挙動は同期的な読み込みと呼ばれます。同期的に読み込まれる JavaScript は HTML の解析をブロックするため、DOMContentLoaded や load イベントの発火タイミングに影響を与えます。

全体として、外部リソースの読み込みはページのライフサイクルに密接に関連しています。外部リソースの読み込み時間が短いほど、ライフサイクルイベントの発火も速くなります。

## readyState & readystatechange

`readyState` と `readystatechange` は、ブラウザが文書やネットワークリクエスト（例：AJAX リクエスト）の状態を追跡するための主要なプロパティとイベントであり、ウェブページ読み込み中の異なる段階を理解し、それに応じた操作を実行するのに役立ちます。主に文書の読み込みやネットワークリクエストの文脈で使用されます。

`document.readyState` は、現在の文書の読み込み状態を示すプロパティです。3 つの可能な値があり、それぞれ異なる読み込み段階を表します。

1. **loading**：文書が読み込み中であり、HTML の解析が進行中で、DOM ツリーが完全に構築されていない状態。
2. **interactive**：HTML 文書が完全に読み込まれ解析され、DOM ツリーが構築された状態。ただし、スタイルシート、画像、サブフレームなどのリソースは完全に読み込まれていない場合があります。
3. **complete**：HTML、CSS、JavaScript、画像、サブフレームなどのすべてのリソースが完全に読み込まれ、ページが完全に準備が整った状態。

開発者は `document.readyState` を使用して現在の文書の読み込み状態を確認し、それぞれの状態に基づいて適切な操作を実行できます。例えば：

```js
if (document.readyState === 'complete') {
  // ページが完全に読み込まれたので操作を実行
}
```

`readystatechange` は `readyState` プロパティに関連するイベントであり、文書の読み込み状態（`readyState`）が変化したときに発生します。開発者は `readystatechange` イベントをリッスンすることで、文書の異なる読み込み段階で特定のロジックを実行できます。以下はその例です：

```js
document.addEventListener('readystatechange', function () {
  if (document.readyState === 'interactive') {
    // DOMツリーが構築された状態
    console.log('DOMが完全に解析されました');
  } else if (document.readyState === 'complete') {
    // ページが完全に読み込まれた状態
    console.log('ページとリソースが完全に読み込まれました');
  }
});
```

以下は、`document.readyState` と `readystatechange` イベントを使用して文書の読み込みの異なる段階を追跡する HTML コードの詳細な例です。ページには基本的な HTML 要素が含まれ、`readyState` の異なる状態に応じた情報が表示されます。

```html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document ReadyStateの例</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        padding: 20px;
      }
      .status {
        font-size: 1.2em;
        color: #333;
        margin: 20px 0;
      }
      img {
        max-width: 100%;
        height: auto;
      }
    </style>
  </head>
  <body>
    <h1>こんにちは！</h1>
    <script>
      function updateStatus() {
        console.log(document.readyState);

        switch (document.readyState) {
          case 'loading':
            console.log('読み込み中');
            break;
          case 'interactive':
            console.log('インタラクティブ');
            break;
          case 'complete':
            console.log('完全に読み込み済み');
            break;
        }
      }

      updateStatus();

      document.addEventListener('readystatechange', updateStatus);
    </script>
  </body>
</html>
```

最終的なコードの出力例：

```
loading
loading
interactive
interactive
complete
complete
```

## DOMContentLoaded イベント

`DOMContentLoaded` イベントは、ブラウザが HTML 文書を読み込む際に発生する重要なイベントで、HTML 文書内のすべての要素が完全に解析され、DOM ツリーが構築されたことを示します。ただし、外部リソース（画像、スタイルシート、動画など）はまだ完全に読み込まれていない可能性があります。これは `load` イベントとの主な違いです。`DOMContentLoaded` イベントは `document` オブジェクト上で発生します。これをキャプチャするには、`addEventListener` を使用する必要があります：

```js
document.addEventListener('DOMContentLoaded', () => {});
```

`DOMContentLoaded` イベントが発生するタイミングは、ブラウザが HTML 文書の解析を終了し、すべての DOM ノードが生成されたときです。外部リソース（画像、動画、スタイルシート、フォントファイルなど）の完全な読み込みは要求されません。

たとえば、ページ内に大きな画像が含まれている場合、`DOMContentLoaded` イベントはその画像の読み込み前に発生しますが、この時点で DOM ツリーは完全に構築されているため、開発者はページ内の DOM 要素を操作してアクセスできます。以下のコード例をご覧ください：

```html
<script type="text/javascript">
  function ready() {
    console.log('DOMが準備完了しました。');

    const img = document.querySelector('#img');

    // 画像は現在読み込み中（またはキャッシュされていない場合）、したがって画像サイズは0x0
    console.log(`画像サイズ: ${img.offsetWidth}x${img.offsetHeight}`);
  }

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

<img
  id="img"
  src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"
/>
```

最終的な出力は以下の通りです。画像の表示は最後になります：

```
DOMが準備完了しました。
画像サイズ: 0x0
```

ページ内に同期的な JavaScript ファイル（`async` や `defer` を使用しない）がある場合、ブラウザは `<script>` タグに遭遇すると HTML 解析を一時停止し、スクリプトの実行が終了するまで待機します。これにより、`DOMContentLoaded` イベントの発火が遅れることがあります。

```html
<script type="text/javascript">
  document.addEventListener('DOMContentLoaded', () => {
    console.log('DOM準備完了！');
  });
</script>

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

<script type="text/javascript">
  console.log('ライブラリが読み込まれ、インラインスクリプトが実行されました');
</script>
```

出力順序は以下のようになります：

1. ライブラリが読み込まれ、インラインスクリプトが実行されました
2. DOM 準備完了！

`DOMContentLoaded` をブロックしないスクリプトには以下の特徴があります：

- `async` 属性を持つスクリプト
- `document.createElement('script')` を使用して動的に生成され、ページに追加されたスクリプト

## window.onload イベント

ページ全体（スタイル、画像、その他のリソースを含む）が完全に読み込まれた際に、`window` オブジェクト上で `load` イベントが発生します。このイベントは、`onload` プロパティを使用して取得できます。

以下の例では、画像サイズを正しく表示します。なぜなら、`window.onload` はすべての画像が完全に読み込まれるのを待つからです：

```html
<script type="text/javascript">
  window.onload = function () {
    console.log('ページが読み込まれました');

    // この時点で画像はすでに読み込み完了
    console.log(`画像サイズ: ${img.offsetWidth}x${img.offsetHeight}`);
  };
</script>

<img
  id="img"
  src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"
/>
```

この場合、出力結果は以下の通りです。`onload` は画像などのリソースの完全な読み込み後に発火するため、画像サイズを取得できます：

```
ページが読み込まれました
画像サイズ: 544x184
```

## window.onbeforeunload イベント

`beforeunload` イベントは、ページが離れる直前（ブラウザがページをアンロードする直前）に発生します。たとえば、ユーザーがページを離れる、閉じる、またはリフレッシュする際に発生します。このイベントを使用すると、開発者はユーザーに「本当にページを離れるかどうか」を確認させるプロンプトを表示できます。通常、未保存データの警告やユーザー作業の損失を防ぐために使用されます。

このイベントでは、ブラウザがカスタムメッセージを表示することは許可されていません。代わりに、ブラウザのデフォルトの警告ダイアログが表示されます。たとえば、ユーザーが未保存のフォーム入力を行った場合、以下のコードを使用してページ離脱を防ぐことができます：

```js
window.onbeforeunload = function () {
  return false; // 離脱を確認するメッセージを表示
};
```

この例では、ユーザーがページを離れようとするときに警告ダイアログが表示されます。ただし、現代のブラウザではカスタムの警告メッセージは無視され、標準の警告ダイアログが表示されます。

また、このイベントの乱用や頻繁な使用はユーザー体験を損なう可能性があるため、未保存データがある場合など必要な場合にのみ使用することが推奨されます。

## unload イベント

`unload` イベントは、ページが完全にアンロードされる際（ページが閉じられる、リフレッシュされる、または新しいページに遷移する際）に発生します。このイベントは `beforeunload` と異なり、ページ離脱を防ぐことはできませんが、最終的なクリーンアップ操作を実行するために使用されます。

たとえば、`unload` イベントは、WebSocket 接続の終了、メモリの解放、または非同期リクエストのキャンセルに使用されます。このイベントはページがアンロードされるタイミングで確実に実行されるため、リソースの解放に役立ちます。

以下の例は、ページを離れる際に分析データをサーバーに送信する方法を示しています。この場合、`navigator.sendBeacon` メソッドを使用することで、ページがアンロードされた後でもデータを非同期的に送信できます：

```js
const analyticsData = {
  // 収集したデータオブジェクト
};

window.addEventListener('unload', function () {
  navigator.sendBeacon('/analytics', JSON.stringify(analyticsData));
});
```

`sendBeacon` のリクエストが完了するころには、ブラウザはすでにページを離れている可能性があります。このメソッドは主に、短時間で完了する分析データの送信に使用されます。

## 総括

HTML の解析はページライフサイクルの基盤ですが、それ自体は JavaScript で監視可能なライフサイクルイベントには含まれません。`DOMContentLoaded` イベントは DOM ツリーが構築されたタイミングで発生し、`load` イベントはページのすべてのリソースが完全に読み込まれた後に発生します。また、`beforeunload` イベントはページ離脱前に警告を表示するために使用され、`unload` イベントはページが完全にアンロードされた際にリソースをクリーンアップするために使用されます。

これらのイベントは、開発者がページの読み込みや終了プロセスを制御するための手段を提供し、ユーザー体験の向上やページパフォーマンスの改善に役立ちます。

---

### [私たちはLeapcell、Node.jsプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_htmlcycle)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/6f40f839-94cd-e15e-7b72-92f8fc7ad027.png)](https://leapcell.io/?lc_t=q_htmlcycle)

[Leapcell](https://leapcell.io/?lc_t=q_htmlcycle)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/d27027e1-032b-8006-1fda-aa0663b19ce0.png)](https://leapcell.io/?lc_t=q_htmlcycle)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/lifecycle-of-html)