【実務・中級編】 画像デコードとレンダリングの最適化 – Webブラウザの仕組み実践ガイド

「なぜかスクロールがカクつく」を卒業する。画像デコードの裏側と `decode()` の魔法

現場でフロントエンドをしていると、一度は経験するはずだ。「ページは表示されたのに、一瞬だけカクつく」「インタラクションが重い」。原因を探ると、多くの場合、画像が犯人だ。

今日は、ブラウザが裏で何をしているのか、そして私たちがどうやってそれを制御すべきかについて、少し深い話をしよう。

ブラウザの「メインスレッド」という名の独裁者

まず、ブラウザのレンダリングエンジンがどう動いているか、大雑把に頭に入れておいてほしい。HTMLを解析してDOMを作り、CSSを当ててCSSOMを作り、それらをマージしてRender Treeを作る。ここまではいい。

問題は、その後の「ペイント」の手前にある「画像デコード」という泥臭い処理だ。

ブラウザは、``タグやCSSの`background-image`で指定された圧縮画像(JPEGやWebPなど)を、画面に描画できる「ビットマップデータ」に変換しなければならない。このデコード処理、実は結構な負荷がかかる。しかも厄介なことに、デフォルトの状態では、このデコードが「メインスレッド」で行われることが多いんだ。

メインスレッドは、JavaScriptの実行、イベントハンドリング、スタイルの計算、レイアウトの計算をすべて一人でこなす独裁者だ。ここに「重い画像のデコード」というタスクを放り込むと、メインスレッドは他の処理を後回しにする。結果、ユーザーが画面をスクロールしようとしても反応せず、カクつき(Jank)が発生する。これが「画像が重い」の正体だ。

`decode()` メソッドによる「非同期デコード」という最適解

最近のブラウザは賢いので、ある程度は勝手に最適化してくれる。だが、大規模なWebアプリや、高解像度の画像が並ぶギャラリーサイトでは、ブラウザの「善意」に頼るのは危険だ。

そこで登場するのが `HTMLImageElement.prototype.decode()` だ。これを使えば、画像が描画される前に、ブラウザに「バックグラウンドでデコードしといてくれ」と指示を出せるようになる。

使い方は驚くほどシンプルだ。

/

  • 画像を非同期で読み込み、デコードしてからDOMに追加する関数
  • @param {string} src – 画像のURL

/
async function loadAndDecodeImage(src) {
const img = new Image();
img.src = src;

try {
// ブラウザに「メインスレッドを止めずにデコードしてくれ」と依頼する
// このPromiseが解決されるまで、画像はまだ描画されない
await img.decode();

// デコードが終わった瞬間にDOMに追加すれば、
// 画面がカクつくリスクを最小限に抑えられる
document.body.appendChild(img);
console.log(‘デコード完了!描画準備OKです。’);
} catch (error) {
console.error(‘デコード中にエラーが発生しました’, error);
}
}

// 実際に使う時はこんな感じ
loadAndDecodeImage(‘high-res-photo.jpg’);

なぜこれだけで劇的に変わるのか?

このコードのポイントは、「DOMに追加する前」にデコードを完了させている点にある。

通常、`document.body.appendChild(img)` を実行した瞬間、ブラウザは「おっと、表示しなきゃ!」と慌ててデコードを開始する。これがレンダリングの最中だとメインスレッドを占有してしまう。

しかし、`await img.decode()` を挟むことで、デコードという重たい処理を非同期に逃がし、「準備ができたものだけを、スムーズに描画に乗せる」というスマートな制御が可能になるんだ。

実務で戦う君たちへ:現場の注意点

この手法は魔法ではない。いくつか押さえておくべき「現場のリアル」がある。

1. 過剰な最適化は避ける: すべてのサムネイルにこれを実装する必要はない。メインビジュアルや、ユーザーがクリックした直後に表示される巨大なモーダル画像など、「滑らかさがUXに直結する箇所」に絞って使おう。
2. メモリ管理を忘れない: `new Image()` で作成したオブジェクトは、適切に扱わないとメモリリークの温床になる。不要になったら `null` を代入して参照を外すのを忘れないように。
3. ブラウザ互換性: `decode()` はモダンブラウザでは標準的にサポートされているが、古い環境を考慮するなら、素直に `HTMLImageElement` の `onload` イベントと組み合わせるレガシーな手法との併用を検討してほしい。

最後に

フロントエンドの最適化は、ブラウザを「ブラックボックス」として扱うのではなく、その裏側で何が起きているかを想像する力から始まる。

「画像が表示されないから重いんじゃない、表示するための準備が重いんだ」という視点を持つだけで、君が書くコードの品質は一段階上がるはずだ。ぜひ次のプロジェクトで試してみてほしい。また何か壁にぶつかったら、いつでも聞いてくれ。応援しているよ。

コメント

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