画像が読み込まれたタイミングで「フェードインさせる」「ローディングを解除する」「失敗したら代替画像に差し替える」といった処理は、load / error イベントで実現できます。ただしキャッシュ済み画像や読み込み失敗の判定にはいくつか見落としやすい落とし穴があります。この記事では確実に判定するための実装を整理します。
loadイベントで読み込み完了を検知する基本- キャッシュ済みでも取りこぼさない
completeの使い方 completeだけでは成功判定にならない理由(naturalWidth)onerrorでの代替画像(無限ループの防止)- 動的生成でハンドラを
srcより前に設定する理由 decode()と、複数画像をまとめて待つPromise.all
img.addEventListener("load", ...)、すでに読み込み済みかは img.complete で確認します。ただし complete は失敗時も true になるため、成功判定は img.complete && img.naturalWidth > 0 で行います。動的生成ではハンドラを src 代入より前に設定してください。基本:load イベントで読み込み完了を検知する
最も基本的な方法は load イベントです。img.onload = ... でも書けますが、複数のハンドラを登録できる addEventListener の方が安全です。
<img id="my-image" src="sample.jpg" alt="サンプル画像">
const img = document.getElementById("my-image");
img.addEventListener("load", () => {
console.log("画像の読み込みが完了しました");
// 表示後の処理(フェードイン・ローディング解除など)
});
img.addEventListener("error", () => {
console.error("画像の読み込みに失敗しました");
});
すでに読み込み済みか判定する(complete の罠)
画像がキャッシュ済みなどの理由で、JavaScriptが動く前に読み込みを終えていると、あとから登録した load ハンドラは発火しません。この取りこぼしを防ぐには img.complete を先に確認します。
complete === true は「成功」ではないimg.complete は読み込みに失敗しても trueになり、src が未設定でも true です。「読み込めたか」を確実に判定するには、実際に画像の幅が取れたかを表す naturalWidth > 0 と組み合わせます。const img = document.getElementById("my-image");
if (img.complete) {
// complete でも naturalWidth が 0 なら「失敗」
if (img.naturalWidth > 0) {
onLoaded(); // 読み込み成功
} else {
onError(); // 読み込み失敗
}
} else {
img.addEventListener("load", onLoaded, { once: true });
img.addEventListener("error", onError, { once: true });
}
function onLoaded() { console.log("読み込み成功"); }
function onError() { console.error("読み込み失敗"); }
読み込み失敗時に代替画像へ差し替える
画像が存在しない・ネットワークエラーなどで失敗したら、error イベントで代替画像に差し替えます。このとき代替画像まで失敗すると無限ループになるため、一度差し替えたらハンドラを外しておきます。
const img = document.getElementById("my-image");
img.addEventListener("error", function onError() {
// 代替画像も失敗したときの無限ループを防ぐため、先に解除
img.removeEventListener("error", onError);
img.src = "/img/fallback.png";
});
クリックで画像を切り替える場合のエラー代替や src 操作についてはimgタグのsrcを書き換えて画像を変更する完全ガイドも参考になります。
動的に画像を生成する場合の注意点
JavaScriptで new Image() を使って読み込む場合、ハンドラ(load / error)を src 代入より前に設定するのが鉄則です。キャッシュ済み画像は src を入れた瞬間に読み込みが完了することがあり、src の後にハンドラを付けると load を取り逃します。
const img = new Image();
// 1. 先にハンドラを登録
img.addEventListener("load", () => {
console.log("動的画像の読み込み完了");
document.body.appendChild(img);
});
img.addEventListener("error", () => {
console.error("動的画像の読み込み失敗");
});
// 2. 最後に src を設定(ここで読み込みが始まる)
img.src = "sample.jpg";
img.src = ... を先に書いてからハンドラを登録すると、キャッシュヒット時に読み込み完了がハンドラ登録より先に起きて発火を逃すことがあります。「ハンドラ → src」の順を徹底してください。decode() で「描画準備完了」を待つ
大きな画像をDOMに追加した瞬間にデコード処理でカクつくことがあります。img.decode() はデコードまで終わって安全に描画できる状態を Promise で待てるため、チラつきのない表示に向いています。
const img = new Image();
img.src = "large.jpg";
img.decode()
.then(() => {
document.body.appendChild(img); // デコード済みなので追加してもカクつかない
})
.catch(() => {
console.error("画像の読み込み/デコードに失敗しました");
});
decode() はエラーも拾えるdecode() が返す Promise は、読み込みやデコードに失敗すると reject されます。成功・失敗を then / catch でまとめて書けるのが利点です。なお decode() はイベントではなく現在の src をデコードする Promise なので、load ハンドラと違って src の後に呼んで問題ありません。複数画像すべての読み込みを待つ
「全画像が揃ってから一斉に表示したい」場合は、1枚ずつの判定を Promise にまとめます。complete の取りこぼし対策も入れた再利用関数にしておくと便利です。
// 1枚の読み込み完了を待つ(成功で resolve、失敗で reject)
function whenImageReady(img) {
return new Promise((resolve, reject) => {
if (img.complete) {
img.naturalWidth > 0 ? resolve(img) : reject(img);
return;
}
img.addEventListener("load", () => resolve(img), { once: true });
img.addEventListener("error", () => reject(img), { once: true });
});
}
const images = document.querySelectorAll("img");
Promise.all([...images].map(whenImageReady))
.then(() => console.log("すべての画像が読み込み完了"))
.catch(() => console.log("一部の画像が失敗しました"));
Promise.all は1枚でも失敗するとその時点で reject します。「成功・失敗を問わず全画像の結果を待ちたい」場合は Promise.allSettled を使い、各要素の status で成否を確認してください。「遅延読み込み(Lazyload)」とは別の話
この記事は「読み込みが終わったかを検知する」処理です。一方、画面に入るまで読み込みを遅らせて初期表示を軽くするのが遅延読み込みで、Lazyload(遅延読み込み)完全ガイド(loading属性・IntersectionObserver)やIntersection Observerを使った画像の遅延読み込みが該当します。両者は組み合わせて使えます。画像をファイルから読み込むケースは画像をドラッグ&ドロップで読み込む方法も参考にしてください。
よくある質問(FAQ)
img.addEventListener("load", ...) で検知します。JavaScriptが動く前に読み込みが終わっている可能性があるなら、先に img.complete を確認し、true なら直接処理を実行します。complete は読み込み失敗でも true になります。成功したかどうかは img.naturalWidth > 0 で確認してください。naturalWidth が 0 なら読み込みに失敗しています。error イベントで img.src を代替画像に差し替えます。ただし代替画像も失敗すると無限ループになるため、差し替える前に removeEventListener でハンドラを外しておきます。src 代入より後に登録していませんか。キャッシュ済み画像は src を入れた瞬間に完了することがあるため、load / error は必ず src の前に設定してください。Promise.all でまとめて待ちます。失敗があっても全部の結果を待ちたい場合は Promise.allSettled を使ってください。ページ全体の読み込みタイミングはページロード時に処理を実行する完全ガイドを参照してください。まとめ
- 完了検知:
img.addEventListener("load", ...)(.onloadより堅牢) - 読み込み済み判定:
img.complete。ただし成功判定はnaturalWidth > 0を併用 - 失敗対応:
errorで代替画像(無限ループ防止に解除) - 動的生成:ハンドラを
srcより前に設定 - 滑らかな表示:
decode()で描画準備を待つ - 複数画像:
Promise.all/Promise.allSettledで待機
キャッシュと失敗の2つを正しく扱えば、画像の読み込み状況に応じた処理を安定して実装できます。

