DOMContentLoaded とは何か
DOMContentLoaded は、「ブラウザが HTML の構文解析を終えて、DOM ツリーの構築が完了した瞬間」に発火するイベントです。ここが重要です:画像やスタイルのダウンロード完了は待ちません。つまり「画面に必要なノードが全部そろった」タイミングで、要素取得や挿入・変更など、DOM 操作を安全に始められます。
何が“完了”で、何が“未完”なのか
DOMContentLoaded が発火した時点で、document 内の要素は生成済みなので、querySelector などで安全に取得できます。一方で、画像のロードや外部スタイルの読み込み、iframe の読み込みは完了を保証しません。ここが重要です:DOM 操作は DOMContentLoaded、画像などリソース完成待ちの処理は load(window の load イベント)と役割を分けると、無駄な待ち時間が減り、初期化が軽くなります。
使い方の基本(イベント登録と初期化)
最低限のパターン
<script>
document.addEventListener("DOMContentLoaded", () => {
const title = document.querySelector("#title");
title.textContent = "ようこそ"; // 安全にDOMへアクセスできる
});
</script>
HTMLページの HTML を読み終え、DOM が組み上がった直後にコールバックが呼ばれます。初期化処理や要素の取得・イベント登録は、この中で行うと確実です。
スクリプト位置と defer の関係
<!-- 推奨:defer を付けると、HTML 解析が終わるまで実行を遅延し、順序も保持される -->
<script src="app.js" defer></script>
HTMLdefer を付けた外部スクリプトは、DOM 構築完了直前に実行されます。多くのケースで DOMContentLoaded を待たずに、直書きで初期化しても安全に動きます。ここが重要です:複数スクリプトの順序保証や、DOM 構築との相性を考えるなら defer が最も扱いやすい選択です。
DOMContentLoaded と load の違い(タイミングの線引き)
DOMContentLoaded は「DOM 構築完了」で発火し、window の load は「すべての外部リソース(画像・スタイル・iframe等)のロード完了」で発火します。初期化の多くは DOMContentLoaded がベストタイミングです。ここが重要です:画像サイズに依存するレイアウト計算や、Canvas に画像を描画する等、画像の完全読み込みが前提の処理は window.addEventListener("load", ...) を使うと安全です。
よくある落とし穴と回避策
スクリプトを <head> 直下に置いて即時実行すると、まだ DOM が構築されておらず要素取得が null になることがあります。DOMContentLoaded を待つか、defer を付けて実行タイミングを合わせましょう。また、同じ初期化を重ねて登録するとイベントが二重に動くことがあります。初期化は一度だけ呼ばれる設計にし、必要ならフラグでガードします。ここが重要です:タイミング管理(いつ触るか)と一回性管理(何回触るか)で、初期化の安定性が決まります。
実践例:タブ切り替え UI の初期化
HTML
<div class="tabs">
<button data-tab="a" class="is-active">Tab A</button>
<button data-tab="b">Tab B</button>
</div>
<div id="panel-a">Aの内容</div>
<div id="panel-b" class="is-hidden">Bの内容</div>
HTMLJS(DOMContentLoaded で初期化)
document.addEventListener("DOMContentLoaded", () => {
const tabs = document.querySelector(".tabs");
const panelA = document.querySelector("#panel-a");
const panelB = document.querySelector("#panel-b");
tabs.addEventListener("click", (e) => {
if (!(e.target instanceof HTMLButtonElement)) return;
const key = e.target.dataset.tab;
tabs.querySelectorAll("button").forEach(b => b.classList.remove("is-active"));
e.target.classList.add("is-active");
const showA = key === "a";
panelA.classList.toggle("is-hidden", !showA);
panelB.classList.toggle("is-hidden", showA);
});
});
JavaScriptDOM が完成した直後に要素取得・イベント登録を行い、その後のユーザー操作に即応します。ここが重要です:初期化で「要素の参照を確立」「イベントを一括登録」「クラス切り替えで表示制御」を行うのが、保守しやすいパターンです。
async / defer / DOMContentLoaded の整理(一歩深掘り)
async はダウンロード完了次第すぐ実行され、HTML の解析と競合しやすく、他スクリプトとの順序保証がありません。defer は HTML の解析完了直前に、記述順に実行されます。ここが重要です:DOM 操作中心のスクリプトは defer を基本にし、ネットワークで独立して動かす必要があり、DOM に依存しない計測やログ送信などは async を検討します。DOMContentLoaded を直接待つのは、インラインスクリプトや古いコードの互換で有効ですが、現代的には defer に寄せるとコードが簡潔にまとまります。
まとめ
DOMContentLoaded は「DOM が組み上がった瞬間」に発火し、要素取得やイベント登録などの初期化に最適なタイミングです。画像などの外部リソース完了は待たないため、必要なら load を使い分けます。実装では defer を活用してタイミングと順序を安定させ、初期化は一度だけ・クラス切り替え中心で安全に UI を制御する。この設計感覚が身につけば、ページの立ち上がりは速く、初期化は堅牢で、ユーザー体験は滑らかになります。
