【JavaScript】画像が読み込まれたかを判定して処理を実行する方法|onload・complete・naturalWidth・onerror・decode

【JavaScript】画像が読み込まれたかを判定して処理を実行する方法 JavaScript

画像が読み込まれたタイミングで「フェードインさせる」「ローディングを解除する」「失敗したら代替画像に差し替える」といった処理は、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 の方が安全です。

HTML
<img id="my-image" src="sample.jpg" alt="サンプル画像">
JavaScript:load / error を検知
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 と組み合わせます。
JavaScript:complete + naturalWidth で確実に判定
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 イベントで代替画像に差し替えます。このとき代替画像まで失敗すると無限ループになるため、一度差し替えたらハンドラを外しておきます。

JavaScript:代替画像(無限ループ防止)
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 を取り逃します

JavaScript:ハンドラを先、src を後
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 で待てるため、チラつきのない表示に向いています。

JavaScript:decode() で滑らかに表示
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 の取りこぼし対策も入れた再利用関数にしておくと便利です。

JavaScript:whenImageReady と Promise.all
// 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("一部の画像が失敗しました"));
失敗があっても全部待ちたいなら allSettled
Promise.all1枚でも失敗するとその時点で reject します。「成功・失敗を問わず全画像の結果を待ちたい」場合は Promise.allSettled を使い、各要素の status で成否を確認してください。

「遅延読み込み(Lazyload)」とは別の話

読み込み判定 と 遅延読み込み は目的が違う
この記事は「読み込みが終わったかを検知する」処理です。一方、画面に入るまで読み込みを遅らせて初期表示を軽くするのが遅延読み込みで、Lazyload(遅延読み込み)完全ガイド(loading属性・IntersectionObserver)Intersection Observerを使った画像の遅延読み込みが該当します。両者は組み合わせて使えます。画像をファイルから読み込むケースは画像をドラッグ&ドロップで読み込む方法も参考にしてください。

よくある質問(FAQ)

Q画像の読み込み完了を検知するには?
Aimg.addEventListener("load", ...) で検知します。JavaScriptが動く前に読み込みが終わっている可能性があるなら、先に img.complete を確認し、true なら直接処理を実行します。
Qcomplete が true なのに画像が表示されません。
Acomplete読み込み失敗でも true になります。成功したかどうかは img.naturalWidth > 0 で確認してください。naturalWidth0 なら読み込みに失敗しています。
Q読み込みエラーで代替画像を表示するには?
Aerror イベントで img.src を代替画像に差し替えます。ただし代替画像も失敗すると無限ループになるため、差し替える前に removeEventListener でハンドラを外しておきます。
Q動的に作った画像で load が発火しないことがあります。
Aハンドラを src 代入よりに登録していませんか。キャッシュ済み画像は src を入れた瞬間に完了することがあるため、load / error は必ず srcに設定してください。
Q全画像の読み込みが完了してから処理するには?
A1枚ずつの完了を Promise 化し、Promise.all でまとめて待ちます。失敗があっても全部の結果を待ちたい場合は Promise.allSettled を使ってください。ページ全体の読み込みタイミングはページロード時に処理を実行する完全ガイドを参照してください。

まとめ

  • 完了検知img.addEventListener("load", ...).onload より堅牢)
  • 読み込み済み判定img.complete。ただし成功判定は naturalWidth > 0 を併用
  • 失敗対応error で代替画像(無限ループ防止に解除)
  • 動的生成:ハンドラを src よりに設定
  • 滑らかな表示decode() で描画準備を待つ
  • 複数画像Promise.all / Promise.allSettled で待機

キャッシュと失敗の2つを正しく扱えば、画像の読み込み状況に応じた処理を安定して実装できます。