onerror イベントで img・script 要素の読み込みエラーをうまく検知できなかったら
img
要素や script
要素に対し、onerror
属性を設定したり、addEventListener('error')
とイベントハンドラを設定したりすると、その外部ファイルが読み込めなかった時に特定の処理が行える…。という記事を以前書いた。
この中で、DOMContentLoaded
イベント内で要素を特定して onerror
イベントを一括指定すれば良い、といったことを書いたのだが、コレが上手く動作しない場合があるので、紹介しておく。
目次
- DOMContentLoaded イベントは遅すぎる…
body
要素末尾にscript
要素を置くと軽減できる- 一番安定しているのは
onerror
属性値を HTML 中に書く方法だった - 動的に要素を生成した時、外部ファイルはいつ読み込まれるか
- 動的に追加する要素に Error イベントを仕込むべきタイミング
- 以上
DOMContentLoaded イベントは遅すぎる…
HTML 中に onerror
属性をベタ書きするのは気が引けるので、DOM 構築が完了したところで、element.onerror
に関数を代入しよう、という話だった。onerror
への代入にせよ、addEventListener('error')
で指定するにせよ、どちらでも同じだ。
<head>
<!-- …中略… -->
<script>
// DOM 要素が構築できていない間にイベント追加はできないので DOMContentLoaded まで待つ
document.addEventListener('DOMContentLoaded', () => {
// 画像1
document.getElementById('not-found-1').onerror = () => {
console.log('画像が読み込めなかったよ');
};
// 画像2
document.getElementById('not-found-2').addEventListener('error', () => {
console.log('画像が読み込めなかったよ');
});
});
</script>
</head>
<body>
<img src="not-found-1.jpg" id="not-found-1" alt="画像1">
<img src="not-found-2.jpg" id="not-found-2" alt="画像2">
</body>
DOMContentLoaded を待たないと、対象の要素が存在しないのにイベント設定しようとしてエラーになってしまうので、DOMContentLoaded で良いんじゃないか、と思いきや、これがうまくいかない場合がある。すなわち、この場合だとコンソールログが出力されない場合があるのだ。
どうやら、DOMContentLoaded イベントまで待つと、既に画像やスクリプトの読み込みが終わっており、Error イベントが発火し終わっている場合があるようで、うまく Error イベントを拾えないようだった。
body
要素末尾に script
要素を置くと軽減できる
そこで次のように、body
要素の最後に配置した script
要素にて、DOMContentLoaded
イベントを待たずに DOM 操作してみた。コレなら、スクリプトを読み込む時点で対象の要素がみつからない、ということは回避できるだろう。
<body>
<img src="not-found-1.jpg" id="not-found-1">
<img src="not-found-2.jpg" id="not-found-2">
<script src="not-found-3.js" id="not-found-3"></script>
<script src="not-found-4.js" id="not-found-4"></script>
<script>
document.getElementById('not-found-1').onerror = () => {
console.log('画像が読み込めなかったよ');
};
document.getElementById('not-found-2').addEventListener('error', () => {
console.log('画像が読み込めなかったよ');
});
document.getElementById('not-found-3').onerror = () => {
console.log('スクリプトが読み込めなかったよ');
};
document.getElementById('not-found-4').addEventListener('error', () => {
console.log('スクリプトが読み込めなかったよ');
});
</script>
</body>
この場合、img
要素に関しては、コンソールログが正しく出力された。しかし、script
要素の方はコンソールログが出力されなかった。
script
要素は通常、script
要素が見つかった時点でスクリプトの読み込みを開始し、読み込みが完了するまで画面描画や後続のデータの読み込みをブロックする。そのため、イベントを追加するための script
要素が後ろにあると「もう遅い」のである。
一番安定しているのは onerror
属性値を HTML 中に書く方法だった
img
要素・script
要素いずれの場合でも必ずエラー処理ができるやり方は、HTML 中に onerror
属性を書いておくやり方だった。
<img src="not-found.jpg" onerror="console.log('画像が読み込めなかったよ');">
<script src="not-found.js" onerror="console.log('スクリプトが読み込めなかったよ');"></script>
さて、img
要素と script
要素とで、外部ファイルの読み込みタイミングが少し違うようだった。もう少し検証してみよう。
動的に要素を生成した時、外部ファイルはいつ読み込まれるか
次は、動的に img
要素や script
要素を生成し、src
属性を設定して、HTML 中に appendChild()
してみる。この処理の中で、いつ画像ファイルやスクリプトファイルの読み込みが行われているのか、開発者ツールで見てみた。
// 画像の場合
let img;
function createImg1() { img = document.createElement('img'); }
function createImg2() { img.src = 'something.jpg'; }
function createImg3() { document.body.appendChild(img); }
// スクリプトの場合
let script;
function createScript1() { script = document.createElement('script'); }
function createScript2() { script.src = 'something.jpg'; }
function createScript3() { document.body.appendChild(script); }
これらの関数を1つずつ、画面上に配置したボタンから呼び出してみて、画像ファイルやスクリプトファイルが読み込まれるタイミングを調べた。
Chrome ブラウザで試した結果、img
要素の場合は createImg2()
、つまり src
属性に値が代入された時に読み込みが開始した。一方 script
要素は、src
属性値が決まったタイミングではなく、appendChild()
した時に初めて読み込みが始まった。
動的に追加する要素に Error イベントを仕込むべきタイミング
つまり、動的に追加する要素に対して Error イベントを仕込むには、img
要素は src
属性値を設定する前、script
要素は HTML に追加する前でないと、意図したとおりに動作しないワケだ。
// 画像の場合
function createImg() {
const img = document.createElement('img');
img.onerror = console.log('画像が読み込めなかったよ'); // ← src 属性値が決まる前に設定しておく
img.src = 'something.jpg';
document.body.appendChild(img);
}
// スクリプトの場合
function createScript() {
const script = document.createElement('script');
script.src = 'something.jpg';
script.onerror = console.log('スクリプトが読み込めなかったよ'); // ← HTML に追加する前に設定しておく
document.body.appendChild(script);
}
以上
外部ファイルの読み込みに失敗した時に処理したいことは時々ありうるだろうが、地味に読み込みタイミングとイベントの設定タイミングがキモになるので、注意してほしい。