【JS応用】リソース読み込み: onload と onerror

リソース読み込みの要:onload と onerror による堅牢なフロントエンド設計

現代のWebアプリケーションにおいて、画像、スクリプト、スタイルシート、あるいは外部フォントといったリソースの読み込み制御は、ユーザー体験(UX)と安定性に直結する極めて重要な要素です。ネットワークの不安定さ、CDNの障害、あるいは不適切なリソースパスなど、フロントエンドエンジニアは常に「読み込み失敗」というリスクと隣り合わせにあります。

本稿では、HTML要素におけるリソース読み込みの成否を判定する onload と onerror イベントに焦点を当て、それらをどのように活用して堅牢なアプリケーションを構築すべきか、技術的な深掘りと実務的な知見を共有します。

onload と onerror の技術的背景

ブラウザはHTMLをパースする際、img、script、link、iframe などのタグに遭遇すると、非同期的にリソースの取得を開始します。このとき、ブラウザは「リソースが正常に取得できたか」「取得に失敗したか」を判定し、それぞれのイベントを発火させます。

onload イベントは、リソースがブラウザによって完全にダウンロードされ、レンダリングや実行の準備が整った瞬間に発火します。一方、onerror イベントは、HTTP 404(Not Found)や 500(Internal Server Error)、あるいはネットワーク断絶などにより、リソースの取得が完了できなかった場合に発生します。

特に重要なのは、これらのイベントが「DOMのライフサイクル」と密接に関係している点です。例えば、script タグであれば、onload はスクリプトの実行が完了したタイミングを指します。img タグであれば、画像データのデコードが完了し、表示が可能になったタイミングとなります。これらを正しくハンドリングすることで、リソースの読み込み待ちによるレイアウトシフト(CLS)を防止したり、フォールバック(代替リソースへの切り替え)を自動化したりすることが可能になります。

サンプルコード:堅牢な画像読み込みの実装

単に画像を表示するだけでなく、読み込み失敗時に代替画像(プレースホルダー)を表示し、さらに読み込み完了時にフェードインアニメーションを付与する実装例を紹介します。


/**
 * 画像読み込みを管理する高機能関数
 * @param {HTMLImageElement} imgElement 
 * @param {string} src 
 * @param {string} fallbackSrc 
 */
function loadImageWithFallback(imgElement, src, fallbackSrc) {
  // 読み込み開始時の処理(ローディングスピナーの表示など)
  imgElement.classList.add('is-loading');

  imgElement.onload = () => {
    imgElement.classList.remove('is-loading');
    imgElement.classList.add('is-loaded');
    console.log('画像が正常に読み込まれました');
  };

  imgElement.onerror = () => {
    console.error('画像の読み込みに失敗しました。フォールバックを適用します。');
    imgElement.src = fallbackSrc;
    imgElement.classList.remove('is-loading');
    imgElement.classList.add('is-error');
  };

  imgElement.src = src;
}

// 使用例
const avatar = document.querySelector('#user-avatar');
loadImageWithFallback(avatar, 'user-photo.jpg', 'default-avatar.png');

このコードのポイントは、onerror イベント内で再帰的に src を書き換えるのではなく、一度きりのフォールバックを適用している点です。もし fallbackSrc 自体も読み込めなかった場合、無限ループに陥るリスクがあるため、実務ではエラー回数の管理や、定数としてのフォールバックURLの厳格な管理が求められます。

実務アドバイス:大規模アプリケーションでの落とし穴と対策

実務の現場では、単なる onerror の実装だけでは不十分なケースが多々あります。以下に、プロフェッショナルとして考慮すべき重要な視点を挙げます。

1. イベントリスナーの適切な削除
JavaScript で動的に要素を生成し、onload/onerror を割り当てる場合、要素を DOM から削除する際にイベントリスナーを明示的に解除(null 代入)しないと、メモリリークの原因となります。特に React や Vue などの SPA フレームワークを利用している場合、コンポーネントのアンマウント時にこれらがクリーンアップされているか確認してください。

2. セキュリティと CORS の問題
外部ドメインからリソースを読み込む際、script タグや img タグの onerror は、CORS(Cross-Origin Resource Sharing)の設定によって詳細なエラー情報が取得できない場合があります。セキュリティ上の制限により、ブラウザは「何が起きたか」の詳細を隠蔽することがあります。この場合、開発者ツールでの確認が必須となりますが、本番環境でエラーログを収集したい場合は、サーバー側の Access-Control-Allow-Origin ヘッダー設定を最適化する必要があります。

3. ネットワーク断絶時の挙動
オフライン環境や、非常に低速な回線では、onload がいつまで経っても発火しない「読み込み中」の状態が続きます。これを放置するとユーザーはストレスを感じます。そのため、一定時間経過しても onload が発火しない場合に強制的にエラー処理を実行する「タイムアウト処理」を実装するのが、上級者のアプローチです。


// タイムアウトを実装したリソース読み込みパターン
function fetchWithTimeout(element, src, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error('Timeout'));
    }, timeoutMs);

    element.onload = () => {
      clearTimeout(timer);
      resolve();
    };

    element.onerror = (err) => {
      clearTimeout(timer);
      reject(err);
    };

    element.src = src;
  });
}

このタイムアウトパターンを Promise ベースで構築することで、async/await を用いたクリーンな非同期処理が可能になります。

パフォーマンスへの影響と最適化

リソースの読み込み失敗は、単なる見た目の問題ではありません。特に重要なアセット(メインビジュアルやCSS)が読み込めない場合、ブラウザのレンダリングがブロックされ、ユーザーは白い画面を見続けることになります。

onerror を活用して、失敗した瞬間に「別のCDNに切り替える」あるいは「静的なデフォルトスタイルを適用する」といったリカバリー処理を即座に行うことで、ユーザーが離脱する確率を劇的に下げることができます。また、最近では `` を併用し、リソースの優先順位を制御することで、onload が発火するまでの時間を短縮させる手法も一般的です。

まとめ

onload と onerror は、フロントエンド開発における「守り」の技術です。しかし、これらを使いこなすことは、単にエラーを回避するだけでなく、ネットワークの不確実性を前提とした「レジリエント(回復力のある)なWeb体験」を設計することに他なりません。

今回解説したイベントハンドリングの基礎と、タイムアウト処理やフォールバック戦略を組み合わせることで、ユーザーに対して「常に表示される」という安心感を提供することができます。ブラウザの挙動を深く理解し、予期せぬ事態を想定してコードを書くこと。それが、スペシャリストとしてのフロントエンドエンジニアに求められる最も重要な資質です。

日々の実装において、正常系だけでなく「失敗したとき、ユーザーはどう見えるべきか」を常に想像しながら、これらのイベントを実装してください。その積み重ねが、堅牢で信頼性の高いアプリケーションを生み出す唯一の道です。

コメント

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