JavaScript | DOM 操作:DOM 基礎 – DOM の読み込み順序

JavaScript JavaScript
スポンサーリンク

DOM の読み込み順序とは何か

DOM の読み込み順序は、ブラウザが HTML を上から解析し、DOM ツリーを構築していく過程の時間軸です。重要なのは「ブラウザは文書を一気に読み終えてから作るのではなく、上から順に解析しながら DOM を増やしていく」こと。途中にある <script> が同期的に実行されると解析が止まり、順序に影響します。


基本タイムライン(解析→DOM 構築→イベント発火)

ブラウザは HTML を読み込みながら、要素ごとにノードを生成して DOM ツリーを広げます。全体像は次の流れです。

  • 上から解析を開始(タグを見つけるたびにノードが増える)
  • 同期 <script> があれば、その場で実行してから解析を再開
  • DOM ツリーが完成した時点で DOMContentLoaded が発火
  • 画像やスタイルなど外部リソースの読み込みも全部終わると load が発火

ここが重要です:DOM の操作開始は DOMContentLoaded が合図。画像サイズ依存の処理や外部リソース待ちは load を使います。


スクリプトの配置とブロッキング(head 直書きの影響)

<script> は、その位置で「同期実行」されるのがデフォルトです。早い位置にスクリプトがあると、後ろの要素がまだ DOM に存在せず、取得が null になることがあります。

<!doctype html>
<html>
  <head>
    <script>
      // ここでは body の要素はまだ登場していない可能性が高い
      console.log(document.querySelector("#btn")); // null になりがち
    </script>
  </head>
  <body>
    <button id="btn">押す</button>
  </body>
</html>
HTML

ここが重要です:同期スクリプトは「その場で」DOM 解析を止めます。要素を安全に扱うには、DOMContentLoaded を待つか、スクリプトを後ろ(body の末尾)に置く、あるいは defer を使います。


defer と async の違い(現代的な正攻法)

defer は「HTML の解析を止めず、解析完了直前に、記述順で実行」します。DOM 操作中心のコードは defer が最適です。async は「ダウンロード完了次第すぐ実行」され、HTML 解析と競合し、他スクリプトとの順序保証がありません。

<!-- 推奨:DOM 構築前後の安全なタイミングで順序通りに実行 -->
<script src="app.js" defer></script>

<!-- 解析と競合し順序が崩れがち(DOM 非依存の計測等に向く) -->
<script src="analytics.js" async></script>
HTML

ここが重要です:DOM を触るなら defer。ネットワーク完了次第で単独実行したい(DOM 非依存)処理は asyncdefer を使うと多くのケースで DOMContentLoaded を待たずに安全に初期化できます。


DOMContentLoaded と load の線引き(いつ何を始めるか)

DOMContentLoaded は「DOM ツリーが完成」で発火します。要素の取得、イベント登録、初期描画のためのクラス切り替えなどはここで十分です。load は「画像・CSS・iframe など外部リソースの読み込み完了」で発火します。

<script>
  document.addEventListener("DOMContentLoaded", () => {
    // DOM が揃った直後に初期化
    const img = document.querySelector("#hero");
    const title = document.querySelector("#title");
    title.textContent = "ようこそ";
  });

  window.addEventListener("load", () => {
    // 画像の幅・高さを正しく使う処理などはこちら
    const img = document.querySelector("#hero");
    console.log(img.naturalWidth, img.naturalHeight);
  });
</script>
HTML

ここが重要です:初期化は DOMContentLoaded。画像寸法や外部フォントのロード完了を前提にした処理は load。


CSS とレンダリングの関係(DOM と表示の違いを理解)

外部 CSS の読み込みは、スタイルの計算や初回描画に影響しますが、DOM の構築自体は完了します。つまり「要素は取得できるが、見た目の確定が遅れる」ことがあり、FOUT(フォントの一瞬の置き換わり)やレイアウトジャンプが発生します。

ここが重要です:DOM 操作は先に行い、見た目の遅延は「CSS でのプレースホルダ、クラス切り替え、アニメーション開始のタイミング調整」で吸収します。レイアウト計算(幅・高さ取得)を大量に挟むと性能が落ちるため、読み取りと書き込みはバッチ化します。


実践パターン(安全な初期化の書き方)

defer を使い、DOM 構築完了直前に順序通り実行。DOM に依存する初期化は直に走らせ、画像サイズ依存処理だけ load を併用します。

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="styles.css">
    <script src="main.js" defer></script>
  </head>
  <body>
    <h1 id="title">…</h1>
    <img id="hero" src="hero.jpg" alt="">
    <button id="btn">押す</button>
  </body>
</html>
HTML
// main.js(defer)
(() => {
  // DOM はほぼ完成しているので、直に初期化できる
  const title = document.querySelector("#title");
  const btn = document.querySelector("#btn");

  title.textContent = "ようこそ";
  btn.addEventListener("click", () => title.classList.toggle("active"));

  // 画像サイズが必要なら load を併用
  window.addEventListener("load", () => {
    const hero = document.querySelector("#hero");
    console.log(hero.naturalWidth);
  });
})();
JavaScript

ここが重要です:defer に寄せると、イベント待ち用のボイラープレートが減り、初期化が簡潔になります。必要なときだけ load を追加するのが効率的です。


よくある落とし穴(二重初期化・同期スクリプトの罠)

DOM 構築前に要素へアクセスして nullDOMContentLoaded を複数箇所で重複登録して二重初期化。async と同期 <script> の混在で実行順が乱れ、依存関係が壊れる。これらはすべて「タイミング管理」が原因です。

ここが重要です:依存関係のあるスクリプトは defer に統一し、初期化は一度だけ走る設計(フラグや即時関数)に。DOM を直接触る同期 <script> は原則使わず、どうしても使うなら body 末尾に置くか、DOMContentLoaded でガードします。


まとめ

DOM の読み込み順序は「上から解析し、同期スクリプトで止まり、DOM 完成で DOMContentLoaded、外部リソース完了で load」という時間軸です。DOM 操作中心のコードは defer を基本にし、要素取得・イベント登録は DOMContentLoaded(または defer 実行時)で行う。画像サイズなどリソース依存は load に分離。この線引きを体に入れると、初期化は堅牢になり、不要な待ちやバグが消え、ページは軽快に立ち上がります。

タイトルとURLをコピーしました