13
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「Image.onloadはやめてImage.decodeを使おうね」はループの中以外の話

Last updated at Posted at 2025-04-22

この記事は何?

画像を複数枚jsで読み込む実装を行なっていたところ、
generosenninさんから「onloadよりimage.decodeを使った方がいいよ。」とアドバイスいただきました。

ただ、実際に実装するとなぜかうまくいかなかったので調査した結果をここに記します

発生した事象

for文で回しながら画像を取得する処理において、image.decodeを利用すると、下記のエラーが発生する。
これは特定の画像やタイミングで起きるものではなく、不定期のタイミングで発生する(ただ、必ずに近いレベルで発生した)

EncodingError: The source image cannot be decoded.

再現環境

imagesフォルダに連番で1.png~30.pngを適当に配置する

HTMLを適当に作る

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script src="main.js"></script>
</body>
</html>

Javascript

main.js
for (let index = 0; index < 30; index++) {
  const img = new Image();
  img.src = `./images/${index + 1}.jpeg`;
  img
    .decode()
    .then(() => {
      document.body.appendChild(img);
    })
    .catch((encodingError) => {
      console.error(`画像 ${index + 1} のロード中にエラーが発生しました:`, encodingError);
    });
}

これでindex.htmlを開くと該当のエラーの発生を確認できる。

同じことをimage.onloadでやるとエラーは発生しない。

main.js
for (let index = 0; index < 30; index++) {
  const img = new Image();
  img.src = `./images/${index + 1}.jpeg`;
  img
    .onload = () => {
      document.body.appendChild(img);
    }

  img.onerror = () => {
    console.error(`画像 ${index + 1} のロード中にエラーが発生しました:`, encodingError);
  }
}

考察

ドキュメントをちゃんと読めば書いてありますが、

スクリーンショット 2025-04-19 13.24.16.png

DOMに追加しても"安全になったとき"に解決されるプロミスな訳です。
for文で回している以上、decodeが安全性を確認する前に次のdecodeが実行されてしまい、安全性を解決できずにデコードできない画像が生まれてしまうことが原因でこの問題が起きているようです。

※あくまで私の意見なので、違ったら教えてください。

じゃあどういう時にdecodeを使うの?

画像単体の読み込みや、逐次的に画像を読み込む場合(ボタンを押したらロードされる仕組みなど)であれば、onloadよりも優先して利用すべきです。

例えば下記のような仕組みで利用する分には問題ありません。

main.js
let currentImageIndex = 0;
const totalImages = 30;

// 次の画像を読み込む関数
function loadNextImage() {
  if (currentImageIndex >= totalImages) {
    console.log('すべての画像がロードされました');
    return;
  }
  
  const img = new Image();
  img.src = `./images/${currentImageIndex + 1}.jpeg`;
  img.style.width = '200px';
  img.style.height = 'auto';
  img.alt = `画像 ${currentImageIndex + 1}`;
  
  // 画像をデコードして表示
  img.decode()
    .then(() => {
      document.body.appendChild(img);
      console.log(`画像 ${currentImageIndex + 1} をロードしました`);
      currentImageIndex++;
    })
    .catch((encodingError) => {
      console.error(`画像 ${currentImageIndex + 1} のロード中にエラーが発生しました:`, encodingError);
      currentImageIndex++; // エラーが発生した場合も次の画像に進む
    });
}

// ページ読み込み時に最初の画像をロード
document.addEventListener('DOMContentLoaded', () => {
  // ボタンを作成して画面に追加
  const loadButton = document.createElement('button');
  loadButton.textContent = '次の画像を読み込む';
  loadButton.style.display = 'block';
  loadButton.style.margin = '10px 0';
  loadButton.addEventListener('click', loadNextImage);
  document.body.appendChild(loadButton);
  
  // 最初の画像を自動的にロード
  loadNextImage();
});

終わりに

やりたいことに応じて、decodeとonloadを適切に使い分けるのが良さそう。
基本的にはdecodeを使いつつ、大量の画像を一気に呼ぶ分にはonloadでやる方針で実装できると良いですね〜

13
10
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?