やあ。今日も今日とて、プロダクトのパフォーマンスチューニングに頭を悩ませているところかい?
「Lighthouseのスコアがあと少し伸びない」「なんだかスマホでスクロールした時にカクつく(フレームドロップする)瞬間がある」……フロントエンドの現場にいるなら、誰もが一度は直面する泥臭い悩みだよね。
今回は、そんなパフォーマンスのボトルネックになりがちな「画像」、それもブラウザの裏側の動きを知っているだけで劇的に改善できる``タグの `decoding` 属性について、徹底的に深掘りしていこうか。
公式ドキュメントをサラッと読んだだけでは見えてこない、ブラウザのメインスレッドの裏側で何が起きているのか。そして、我々フロントエンドエンジニアがどう立ち回るべきなのかを、実務の視点から紐解いていくよ。
—
なぜ、画像はパフォーマンスの凶器になり得るのか?
まず、敵を知ることから始めよう。
Webページに表示される画像――例えばPNGやJPEG、あるいはWebP。これらはサーバーからネットワーク経由でバイナリデータとしてダウンロードされた後、画面にピクセルとして描画されるまでに、いくつかの「重い関門」を突破しなければならない。
1. ネットワーク転送: 圧縮されたデータを取得する。
2. パース・レイアウト: HTMLが解釈され、DOMツリーができ、CSSOMと合流してレイアウトが決まる。
3. デコード(復元): 圧縮されたバイナリデータを、CPU(またはGPU)が理解できる「生のビットマップデータ(ピクセルの塊)」に展開する。
問題はこの「3. デコード」なんだ。
画像サイズが数メガバイトあったり、高解像度だったりする場合、このデコード処理にはそれなりのCPUパワーと時間がかかる。
ここで、ブラウザのアーキテクチャにおける最大の禁忌を思い出してほしい。
そう、「メインスレッドをブロックするな」という鉄則だ。
ブラウザのメインスレッドは、JavaScriptの実行、スタイルの計算、レイアウト、そしてユーザーからの入力(タップやスクロール)の処理など、生きるために必要なほとんどの仕事を一手に引き受けている。もし、このメインスレッド上で重い画像のデコードを同期的に実行してしまったらどうなるか?
ユーザーが画面をシュッとスクロールした瞬間、メインスレッドが画像デコードに占有され、画面の描画がピタッと止まる。これが、私たちが恐れる「フレームドロップ(カクつき)」の正体さ。ユーザー体験(UX)をドブに捨てるようなものだね。
—
`decoding` 属性の正体:ブラウザへの「お伺い」から「指示」へ
このメインスレッドの呪縛から私たちを解放してくれるのが、HTML5で導入された `decoding` 属性だ。
使い方は極めてシンプルで、`` タグにこう書くだけ。

この `decoding` 属性には、主に3つの値を指定できる。
- `sync`(デフォルト): 同期デコード。画像を読み込んだら、画面に描画する前にメインスレッドで即座にデコードを完了させようとする。
- `async`: 非同期デコード。他のコンテンツ(テキストやレイアウト)の描画を優先し、画像のデコードはバックグラウンドスレッドへ完全にオフロードする。
- `auto`: ブラウザのエンジンにお任せ。大抵のブラウザはデフォルトでよしなに判断する。
「なんだ、ただのヒントか」と思ったそこの君、甘いね。
現代のモダンブラウザ(BlinkやWebKitなど)において、`decoding=”async”` は単なるお伺いではなく、「この画像はメインスレッドをブロックせずに裏でこっそりデコードしてくれ」という強力なディレクティブとして機能する。
ブラウザの裏側で何が起きているのか?
`decoding=”async”` を指定すると、ブラウザのレンダリングエンジンは次のようなスマートな動きをする。
1. DOMツリーとCSSOMツリーが構築され、レイアウト(Reflow)が走る。この時、画像領域の「場所(ボックス)」はすでに確保されている。
2. デフォルト(`sync`)であれば、このタイミングで画像のデコードが割り込み、メインスレッドを一時停止させる。しかし、`async` が指定されている場合、ブラウザはデコードを後回しにし、まずはテキストやUIの描画を最優先で完了させる(=フレームドロップを防ぐ)。
3. バックグラウンドの別スレッドで画像のデコードが完了したタイミングで、画面の該当部分をスッと更新(再描画)する。
結果として、ユーザーはスクロールやタップの滑らかさを一切損なわずに、コンテンツを閲覧できるというわけだ。
—
現場で即実践!ベストプラクティスと使い分け
じゃあ、すべての画像に `decoding=”async”` を貼ればハッピーになれるのか?
……世の中、そんなに甘くないよね。実務では「適材適所」が鉄則だ。間違った使い方をすると、かえってUIのチラつきを生む原因になる。
現場のシニアとして、後輩の君に授けたい使い分けの基準はこうだ。
1. ファーストビューの「主役」画像には、あえて `sync` か `auto` を検討する
ページの最上部(ファーストビュー)にドカンと表示されるメインビジュアル(LCP: Largest Contentful Paint の候補になる画像)に `async` を指定するとどうなるか?
テキストは先に表示されたのに、肝心のメイン画像だけがワンテンポ遅れて「ポッ」と遅れて表示される現象(レイアウトのチラつきや、視覚的な遅延)が起きる可能性がある。
LCPのスコアを極限まで削りたいファーストビューの重要画像については、あえてデフォルト(同期デコード)のままにするか、あわせて `fetchpriority=”high”` を併用して優先度をコントロールするのがプロの技だ。
2. ファーストビュー以外、あるいはリスト形式の画像には必ず `decoding=”async”`
ブログの記事一覧、ECサイトの商品グリッド、無限スクロールのタイムラインなど、画面の下部に位置する画像や、ユーザーがスクロールして初めて視界に入る画像。これらには迷わず `decoding=”async”` を付与しよう。
`loading=”lazy”`(遅延読み込み)と組み合わせることで、ネットワーク帯域もCPUリソースも完全に最適化される。
—
実務でそのまま使えるコード例
では、ReactやVue、あるいはプレーンなHTMLなど、どんな環境でも通用する実践的なマークアップの例を見てみよう。
Welcome to My Site

おすすめの記事一覧
フロントエンドのアーキテクチャ設計について
TypeScriptの高度な型パズル入門
このコードのポイントを整理しておくよ。
1. `width` と `height` の明示: ブラウザが画像をダウンロードする前にアスペクト比を計算できるようにし、レイアウトシフト(CLS)を完全に防ぐ。
2. `loading=”lazy”` とのコンビネーション: 画面外の画像はネットワーク通信自体を遅らせる。
3. `decoding=”async”` の適用: 画面外やリスト内の画像がデコードされる際に、スクロールの滑らかさを犠牲にしないようにする。
4. LCP画像への配慮: ファーストビューの画像には `async` をあえて外している点もポイントだね。
—
まとめ
Webフロントエンドの開発は、突き詰めると「ブラウザという巨大なエンジンの機嫌をいかに損ねず、仕事を分散させるか」のゲームだ。
`decoding=”async”` は、たった15文字の属性を追加するだけで、メインスレッドの負荷を軽減し、ユーザーのスクロールをシルキーな滑らかさに変えてくれる非常に費用対効果の高いテクニック。
「動けばいいや」のコードから一歩抜け出して、こうしたブラウザの内部挙動までコントロールできるようになると、フロントエンドエンジニアとしての実力が一段とグッと上がってくるはずだ。
さあ、今日のデプロイから、プロダクトのコードベースにある `` タグたちを見直してみようか。何か困ったことがあれば、いつでも気軽にエンジニアリングチームに相談してくれよな!

コメント