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


# 20250427追記
どうやらこちらは画像のキャッシュサイズ問題のようです。
下記二件のchromiumのイシューが参考になります（報告いただいた方ありがとうございます。）

https://issues.chromium.org/issues/40676514

https://issues.chromium.org/issues/40792189

ただ、こちらの問題は`won't fix`とされてしまっており、今後も直る見込みはなさそうです。

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

https://qiita.com/generosennin/items/ac7797b2af1baa97886e

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

# 発生した事象
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);
  }
}
```


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

https://developer.mozilla.org/ja/docs/Web/API/HTMLImageElement/decode

![スクリーンショット 2025-04-19 13.24.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303906/8e5b9a69-8e05-453a-8d01-7ae067bab873.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でやる方針で実装できると良いですね〜