「なぜかスクロールがカクつく」を卒業する。画像デコードの裏側と `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` イベントと組み合わせるレガシーな手法との併用を検討してほしい。
最後に
フロントエンドの最適化は、ブラウザを「ブラックボックス」として扱うのではなく、その裏側で何が起きているかを想像する力から始まる。
「画像が表示されないから重いんじゃない、表示するための準備が重いんだ」という視点を持つだけで、君が書くコードの品質は一段階上がるはずだ。ぜひ次のプロジェクトで試してみてほしい。また何か壁にぶつかったら、いつでも聞いてくれ。応援しているよ。

コメント