大量の画像の読込

decoding="async" VS loading="lazy"

2019年12月19日
著者: 竹洞 陽一郎

Webページにおいて、画像は重要な役割を果たしており、高解像度化と利用される枚数は増えていく傾向にあります。
その一方で、画像は、従来どおりの実装をしていると、HTMLのパース処理を遅延させる要因となります。
画像をいかに遅延要因としないようにするかの処理として、decoding="async"とloading="lazy"の詳細と、その違いを解説します。

decoding="async"

decoding属性は、画像のデコード処理に関しての指示をユーザエージェントに明示する属性です。
decoding="async"と指定することで、画像のデコード処理を非同期にバッググラウンド処理し、他のコンテンツの表示処理が終わった時に更新処理で画像を一緒に出します。

記述の仕方としては、以下のようになります。


<img class="hoge" decoding="async" src="/images/hage.jpg" alt="hage">

decoding属性の値と意味
意味
sync他のコンテンツと連動してデコード処理を行う
async他のコンテンツと非連動してデコード処理を行い、他のコンテンツの表示が遅延しないようにする
autoデコード処理について、明示的に指定しない(デフォルト値)

ブラウザの対応状況

HTML Living Standardの仕様として標準化されているため、IEとEdge以外のブラウザで対応しています。
EdgeもChromiumをベースとしたベータ版では対応しています。

Can I Use...でのdecoding属性のブラウザ対応表

HTML Living Standardの仕様、4.8.4.3.4 Decoding imagesについて確認しましょう。

画像のデコード処理

まず、画像のデコード処理について解説が書かれています。

Image data is usually encoded in order to reduce file size.
This means that in order for the user agent to present the image to the screen, the data needs to be decoded.
Decoding is the process which converts an image's media data into a bitmap form, suitable for presentation to the screen.
Note that this process can be slow relative to other processes involved in presenting content.
Thus, the user agent can choose when to perform decoding, in order to create the best user experience.

画像データは通常、ファイルサイズを小さくするために符号化されます。
ユーザエージェントが画面に画像を表示するためには、データを復号化する必要があることを意味します。
デコードとは、画像のメディアデータを、画面表示に適したビットマップ形式に変換するプロセスです。
このプロセスは、コンテンツの表示に関連する他のプロセスに比べて時間がかかる場合があります。
したがって、ユーザエージェントは、最良のユーザエクスペリエンスを生成するために、いつ復号化を実行するかを選択しても良いわけです。

デコードとは、画像のメディアデータを、画面表示に適したビットマップ形式に変換するプロセスであると明確に用語の定義をしています。

画像の同期処理

Image decoding is said to be synchronous if it prevents presentation of other content until it is finished.
Typically, this has an effect of atomically presenting the image and any other content at the same time.
However, this presentation is delayed by the amount of time it takes to perform the decode.

画像復号化は、それが終了するまで他のコンテンツの提示を妨げる場合には、同期であると言われます。
一般に、画像と任意の他のコンテンツを同時にアトミック的(※)に表示する効果を持ちます。
ただし、この表示は、デコードの実行にかかる時間だけ遅延します。

(※訳注 アトミック的とは、atomicityというコンピュータ用語から来ている。一連の処理が不可分の一体として成功・失敗いずれかになり、かつ処理途中の状態に他からアクセスできないこと。)

画像の非同期処理

Image decoding is said to be asynchronous if it does not prevent presentation of other content.
This has an effect of presenting non-image content faster.
However, the image content is missing on screen until the decode finishes.
Once the decode is finished, the screen is updated with the image.

画像復号化は、それが他のコンテンツの表示を妨げない場合、非同期であると言われます。
これには、非イメージコンテンツをより速く表示する効果があります。
ただし、デコードが終了するまで、画面には画像の内容が表示されません。
デコードが完了すると、画面はイメージと共に更新されます。

同期と非同期の違い

In both synchronous and asynchronous decoding modes, the final content is presented to screen after the same amount of time has elapsed.
The main difference is whether the user agent presents non-image content ahead of presenting the final content.

同期と非同期、どちらのデコードモードでも、同じ時間が経過した後に最終的な内容が画面に表示されます。
主な違いは、ユーザエージェントが最終コンテンツを提示する前に非画像コンテンツを提示するかどうかである。

仕様上はこのように書かれていますが、ここに実際の効果において大きな違いがあります。
Webの表示速度を高速化するためには、DOMツリーを如何に高速に構築し、CSSオブジェクトモデルを適用するかが鍵を握ります。
画像のデコーディングを非同期にすることで、HTMLのパース処理で画像のデコーディング時間が入らなくなり、その分、時間が短縮されるのです。

実際、この仕様を提案したAppleのSimon Fraser氏は、"decode" attribute on <img> #1920で、以下のように述べています。

Decoding of large images can block the main thread for hundreds of milliseconds or more, interrupting fluid animations and user interaction. Currently, there's no way for a web author to specify that they want an image to be decoded asynchronously, so there are scenarios where it is impossible to avoid UI stalls.

To solve this problem we propose an "async" attribute on image elements. This attribute is a hint to the UA that the author has requested asynchronous decoding. This implies that if the UA paints an image after the "load" event has fired, but before the image has been decoded, the UA is allowed to not paint the image (rather than block on decoding it).

To notify authors when a decoded image frame is available, we propose firing a new event, "ready", on the image element. This would allow authors who require a fully-decoded image, in content that is sensitive to UI stalls, to wait for the "ready" event before doing something that brings the image into view (such as a CSS transition).

Some images repeatedly decode frames, for example, animated GIFs. In addition, the UA can throw away the decoded frame for a still image, requiring a re-decode. In these cases, we propose that the "ready" event only fires once, the first time a frame is available for display.

ISSUES:
"async" is currently used to imply async loading, and it's possible that we'd want to use it in this sense for images too. Maybe call the new attribute "asyncDecode" or something else.

This only solves the problem for image elements, not CSS images. We could add a CSS property to allow async decoding for CSS images, and fire events on the element to which it applies.

以下に、日本語に訳したものを掲載します。

大きなイメージをデコードすると、メインスレッドが数百ミリ秒以上ブロックされ、滑らかなアニメーションやユーザの操作が中断される可能性があります。現在のところ、Webの作者が画像を非同期にデコードしたいと指定する方法はないため、UIのストールを避けることができないシナリオがあります。

この問題を解決するために、画像要素の「async」属性を提案しました。この属性は、作成者が非同期デコードを要求したことをUAに知らせるヒントです。これは、「load」イベントが発火した後にUAが画像をペイントし、画像がデコードし終える前は、UAはイメージをペイントしないことを許可することを意味します(デコードでブロックするのではなく)。

デコードされた画像フレームが利用可能であるときに作成者に通知するために、画像要素に新しいイベント「ready」を発火させることを提案しました。これにより、完全にデコードされた画像を必要とする、UIストールの影響を受けやすいコンテンツの作成者は、画像を表示する処理を実行する前に「ready」イベントを待機できるようになります(CSSトランジションなど)。

アニメーションGIFなど、フレームを繰り返しデコードする画像もあります。さらに、UAはデコードされたフレームを、再デコードを必要とする静止画像用に破棄することができます。これらの場合、「ready」イベントは、フレームが最初に表示可能になったときに一度だけ発生することを提案します。

問題:
「非同期」は現在、非同期読込を暗示するために使用されていますが、イメージに対してもこの意味で使用することができます。新しい属性には「asyncDecode」のような名前を付けます。

これは、CSSイメージではなく、イメージエレメントの問題を解決するだけです。CSSイメージの非同期デコードを可能にするCSSプロパティを追加し、それが適用されるエレメントでイベントを発生させることができます。

Webページの表示速度を高速化するには、メインスレッドがブロックされない、割り込みされない事が重要です。
メインスレッドの計算処理に割り当てられたCPUに、その仕事を専念させるのです。
画像のデコード処理は、バックグラウンド処理で済ませ、ブラウザが画像以外のコンテンツを準備し終えた後に、画像を入れて更新します。

loading="lazy"

loading属性は、画像やiframeの読込処理に関しての指示をユーザエージェントに明示する属性です。
loading="lazy"と指定することで、ブラウザの非表示領域の画像やiframeの読込処理を遅延読込させます。

記述の仕方としては、以下のようになります。


<img class="hoge" loading="lazy" src="/images/hage.jpg" alt="hage">

loading属性の値と意味
意味
lazyいくつかの条件が合致するまで、画像やiframeを遅延読込させる
eager画像やiframeを即座に読み込ませる(デフォルト)

ブラウザの対応状況

loading="lazy"は、まだ標準化仕様ではなく、GoogleがChromeでネイティブ対応し、同じエンジンを使用するOperaが対応しているのみです。
現在、HTML Living Standardに標準化仕様として入るように議論が行われています。

Can I Use...でのloading属性のブラウザ対応表

仕様化の議論

HTML Living Standardの仕様化については、Lazyload images #3752で行われています。

画像がviewport内にあるのかどうかで、遅延読込をさせるかどうかが決まるのですが、ユーザの操作や、コーディングによって、それがどのように変わるかについては、以下のような記載を入れるべきという意見が出されています。

プロファイリングの比較

実際にどのように処理されるのか、Chrome Developer Toolsで確認しましょう。
Spelldataのトップページで検証しました。
SpelldataのWebサイトは、HTTP/1.1+Keep-aliveで配信しており、HTML、CSS、JavaScript、画像のGzip圧縮はしていません。

動画のthumnail.jpgは、<video>のposter属性で指定している画像なので、decodingもloadingも指定していません。

decoding="async"

decoding="async"で画像処理したページ

Chrome Developer Toolsでプロファイリングすると、画像の読込はメインスレッドの処理には出ていません。
HTMLのパース処理が素早く行われ、その後にCSSのパース処理が行われ、スタイルの再計算の後、レイアウト処理が行われています。
HTMLのパース直後から画像のダウンロードが実行されているのが特徴的です。

loading="lazy"

loading="lazy"で画像処理したページ

Chrome Developer Toolsでプロファイリングすると、decoding="async"と同様に、画像の読込はメインスレッドの処理には出ていません。
HTMLのパース処理が素早く行われ、その後にCSSのパース処理が行われ、スタイルの再計算の後、レイアウト処理が行われています。
HTMLのレイアウト処理直後から画像のダウンロードが実行されているのが特徴的です。

decoding="async"とloading="lazy"を併記するとどうなる?

decoding="async"とloading="lazy"は、「如何にメインスレッドの処理を妨げないか?」を違う思想で実現したものです。
従って、その仕組み上、併記しても両立はしません。
現状は、併記すると、Chromeにおいては、loading="lazy"の方が優先されます。

配信での比較

では、双方について、実際に配信するとどの程度違いが出るかをCatchpointのSynthetic Monitoringで確認しましょう。
以下は3日間の計測のデータです。

東京NTT、東京KDDI、東京USENで、15分に1回の頻度で計測しています。(各ISP毎にn=288)
それらをまとめて累積分布関数で表示しています。

decoding="async"とloading="lazy"で画像処理したページの表示速度の違い

画像をレイアウト処理してから読込が開始する分、loading="lazy"は不利で、decoding="async"より遅延しています。
しかし、loading="lazy"は、viewport内の画像の枚数によって読込処理の画像数が決まるため、viewport内の枚数が少なければ、loading="lazy"の方が高速になる可能性があります。
また、スクロールしたときのテストはできていないので、素早く表示された後、ユーザが画面をスクロールした時にどうなるかは、また別の話です。

まとめ