1ミリ秒でも早く届けたい! HTMLで画像読込を高速化するために取り組んでいること

223
105
464

ウェブサイトの表示スピードはサイトの健全性における重要な観点の一つです。Googleが提唱するCore Web Vitalsコア・ウェブ・バイタルズと呼ばれる指標の中にもサイト表示スピードに関する項目があり、表示されるまでの時間が単なるユーザー体験だけでなく、SEOでも無視できない存在です。表示スピード低下の要因はネットワークやサーバーサイド、そしてフロントエンドまで広範囲におよびます。本記事ではその中でも画像の読み込みについて改善できるテクニックを改善前と改善後を比べながら紹介します。

画像読み込みBefore / Afeter

上図はLighthouseによるチェックの結果です。Lighthouseはウェブサイト検査ツールで、ウェブページのパフォーマンス、アクセシビリティ、SEOなどの状態を計測できます。Google Chromeのデベロッパーツールに搭載されているので手軽に実行できるのも特徴です。

サンプルはもともとシンプルなページなので改善前でもLighthouseのスコアは良好です。しかしながら、改善後はFirst Contentful Paint(FCP)で0.5秒、Largest Contentful Paint(LCP)で0.6秒、Speed Indexでは1.1秒改善されています。画像が14枚程度のシンプルなページでもこれだけの改善があるので、ページが大きくなればなるほどその差は広がっていくでしょう。

読み込み状況をみても、4G回線速度(10Mbs)で改善前は初期読み込み完了まで約2.4秒かかっていたのに対し、改善後は約0.4秒まで短縮しています。転送量についても2.5MBが307kBまで削減しています。

ここからは具体的な改善テクニックを紹介します。

loading="lazy"による遅延読み込み

<img>要素にloading="lazy"属性を用いることで手軽に遅延読み込みを実装できます。この属性がある画像はスクロールしてある程度近づいた時に随時読み込まれるようになります。ただし、ファーストビューに近い範囲の画像(ページ上部からおよそ2000px〜3000px)までは初期読み込みに含まれてしまいます。しかしながら、すぐには表示しないページ下部の画像の読み込みを遅延できるため初期読み込み時の読み込み量を削減するのに効果的です。

少し注意したいのがloading="lazy"属性を使うには<img>要素にきちんとwidth属性とheight属性が記述されている必要があります。さらにwidth属性とheight属性がないと、ブラウザが画像を完全に読み込み終わるまで領域のサイズを計算できません。そのためレイアウトが画像の読み込み後に再度再計算され、レイアウトシフトの原因になります。レイアウトシフトはLighthouseのスコア低下の要因になります。CSSでサイズを指定するからと言って、width属性とheight属性を省くのは避けたほうが良いです。

読み込み優先度

少し応用的な内容になりますが、画像の読み込み優先順位をブラウザ側に伝えられます。fetchpriority属性の値にhighlowといった指定すると高いものから優先的に読み込んでくれるようになります。優先度はデベロッパーツールのネットワークタブから確認できます。「Priority」の項目がない場合はラベル欄を右クリックし、出てくる一覧からPriorityを選べば表示されます。

サンプルの改善例では、ファーストビューで重要なメインビジュアルとロゴの画像にfetchpriority="high"を付与して、すぐには表示する必要ないモバイル用のハンバーガーボタンの画像にfetchpriority="low"を付与しています。

何も指定がないと優先度についてはブラウザが自動で割り振りますが、fetchpriority="high"を明示的に指定することで、指定したものを優先的に読み込むよう設定できます。

上図ではmv_desktop.jpgのみfetchpriorityh="high"を付けた結果です。優先度の表示はhighのまま変わっていないですが、内部的にはより上位の優先度になっており、読み込み順が上がっています。

サンプルの例ではメインの画像を重要な画像やロゴ画像を先に読み込ませることで体感的な表示スピードのアップを図っています。ただし、2022年12月現在、fetchpriority属性に対応しているのはGoogle ChormeとMicrosoft Edgeのみになります。

<picture>要素を使ったデスクトップとモバイル用の画像の出し分け

ファーストビューの画像について、デスクトップとモバイルで違う画像が使われています。この時、両方の画像をCSSのdisplay: noneで表示・非表示を制御すると、使わない方の画像も読み込んでしまいます。つまり、モバイル時でもデスクトップを、デスクトップ時でもモバイルの画像を読み込んでいます

これを回避するために<picture>要素を使って、デバイスに応じた画像の出し分けができます。

<picture>
  <source
    media=" (min-width: 768px)"
    srcset="
      assets/mv_desktop_optimized_1x.jpg 1x,
      assets/mv_desktop_optimized_2x.jpg 2x
    "
  />
  <source
    media=" (max-width: 768px)"
    srcset="assets/mv_mobile_optimized.jpg"
  />
  <img
    src="assets/mv_desktop_optimized_1x.jpg"
    alt="猫たち"
    width="1024"
    height="512"
  />
</picture>

<source>要素とmedia属性を使ってデスクトップ用・デバイス用の画像をそれぞれ指定できます。ブラウザサイズが変わってモバイルサイズになった場合でも自動的にモバイル用画像を取得するので、画像がリンク切れになる心配もありません。詳しい使い方は『imgタグのsrcset・sizes属性とpictureタグの使い方 レスポンシブイメージで画像表示を最適化』にて解説しています。

解像度に応じた出し分け

最近ではデスクトップでも高解像度を持つディスプレイが出てきました。改善前のファーストビュー画像は高解像度用に大きいサイズ(2048px × 1024px)の画像を使っています。この場合、高解像度ディスプレイでない人ユーザーにはむだに大きな画像を読み込むことになってしまいます。そこでユーザーのディスプレイ解像度に応じた画像を出し分けます。

srcset属性というsrc属性に似た属性があります。これは複数候補の画像をカンマ区切りで指定できます。さきほどのデスクトップ用画像にも指定されています。

<source
    media=" (min-width: 768px)"
    srcset="
      assets/mv_desktop_optimized_1x.jpg 1x,
      assets/mv_desktop_optimized_2x.jpg 2x
    "
  />

画像パスの後ろにピクセル密度記述子と呼ばれる1xのようなxを用いた指定をすることでディスプレイの解像度に応じた画像を指定できます。上記では通常解像度のディスプレイにはmv_desktop_optimized_1x.jpgの標準サイズの画像を、2倍の解像度をもつディスプレイにはmv_desktop_optimized_2x.jpgという倍の大きさを持つ画像を指定しています。

画像を最適化する

改善前のJPEG画像はAdobe Photoshopで元データから画質を80で設定して書き出したものです。決して悪い設定ではないのですが、Squooshimageminといった画像圧縮ツールを使うと、少しの劣化で大幅なファイルサイズの削減が実現できます。

上図はSquooshにてMozJPEGというエンコード方式を用いて最適化を行ったものになります。改善前は277KBですが、改善後は86KBまで小さくなり、約69%の削減に成功しています。改善前後で目立った劣化は発生していません。

なお、画像の最適化を行う場合にはすでにJPEGで書き出したものに最適化するのではなく、劣化のない元データから圧縮すると良いです。JPEG画像自体が非可逆圧縮なので、書き出されたJPEG画像は元データから劣化しています。そこにさらに変換をかけると圧縮につぐ圧縮でより劣化しやすくなるためです。もし、元データから直接最適化できない場合は、無劣化のPNG画像に一旦書き出したものへ最適化を行うと良いです。

Squooshは比較しながら細かい最適化設定ができ手軽で便利ですが多くの画像を手作業で変換しようとすると大変です。個別の設定は難しいですがCLIツールやNode.jsで動かせるライブラリもあるので、それらを使うと一気に圧縮できます。Node.js版については記事『グーグルが開発した画像圧縮ツールSquoosh。フロント開発向けにNode.jsで扱う方法まとめ』にて詳しく解説しています。他にも、imageminはwebpack用のプラグインもあるのでバンドラーを利用している方はそちらも便利です。

最適な画像ファイル形式

画像に適した形式を選択することでファイルサイズの削減が見込めます。一般的な画像形式の使い分けとして、ロゴや図表のような色数が少なくシンプルな画像はPNG形式が、写真のような色数が多く複雑な画像はJPEG形式がそれぞれ適しています。それらに加え、WebP形式とSVG形式についても紹介します。

WebP形式

WebP形式は透過・可逆圧縮・非可逆圧縮などができる画像ファイル形式で、JPEGなどに比べて高い圧縮率が特徴です。写真のような画像に適したJPEG形式ですが、透過機能がないので透過がある画像を使う場合にはPNG形式を使うことも多いでしょう。たとえばサンプルのファーストビュー下の「OUR MISSIONS」部分の画像はアニメーションさせるため、背景と透過した猫の画像を分けています。

改善前はPNG画像を使っていますが、PNGは写真のような画像で使うとファイルサイズが肥大しがちです。そこで、改善後はWebP形式を利用しています。WebP画像は透過も可能でPNG画像に比べると大幅な圧縮になります。

改善後の画像はWebPの非可逆圧縮設定を利用しているのでPNG画像と比べると元画像から劣化は発生しています。しかし、実用上は問題のないレベルです。またWebPは可逆圧縮設定もでき、PNGと同様の無劣化圧縮も可能です。この場合は非可逆圧縮よりはファイルサイズが大きくなりますが、それでもPNG画像よりは小さくなります。

WebP形式については記事『次世代画像形式のWebP、そしてAVIFへ –変わり続ける技術に対応するweb制作の黄金解–』にて詳しく解説しています。AVIF形式についてはWebPよりも高い性能を持っていますが、2022年12月現在でMicrosoft Edgeが対応しておらず実戦投入にはやや時期尚早かと思われます。

コラム:全部WebPでいい?

2020年以降リリースされたすべてのモダンブラウザでWebP形式が利用できます。そのためすべての画像をよりファイルサイズ効率の優れたWebPに置き換えても問題ない環境になりました。ただ、実用上は最適化部分で紹介したMozJPEGエンコードでも十分サイズを削減でき、他にもWebPへの変換の手間を考えるとJPEG画像でも十分な場面も多いです。

透過写真などではWebPへ変換したほうが大きなメリットがありますが、それ以外については工数との相談になるかと思います。この点はデザインツール側の対応や便利ツールの登場など、より手軽なWebPへの変換ワークフローが確立されると状況は変わってくるかもしれません。

フレームワークの利用前提になりますが、Next.jsにおけるnext/imageやNuxtにおけるNuxt Imageのような画像最適化ツールは実装コストを下げてくれる有用なツールです。

SVG形式

SVG画像はベクターデータなので表示を大きくしても粗くならないのが特徴です。PNG画像と同様にシンプルな画像が得意ですが、中でもロゴやアイコンなどが最適です。

たとえば、サンプルではファーストビューの真ん中に大きくロゴを載せています。改善前は高解像度対応も含めて大きめなサイズで書き出しており、ファイルサイズは10KBでした。これをSVG形式に変換すると4KBにまで低減できます。ほかにもサンプルページの中盤「Service」の部分に肉球マークが背景として使われていますが、PNG画像は6KBに対し、SVG画像は521バイトになります。単純な図形ほど、表示サイズが大きくなるほどSVGが有効になります。

しかしシンプルでも文字が多い画像になると、たとえ大きな画像でもPNG画像のほうが軽くなることもあります。シンプルな画像でも、ロゴやアイコンはSVG、説明的な図版はPNGが良さそうです。

background-imageと<img>要素

画像の表示方法としてCSSのbackground-imageプロパティを使った方法もあります。どちらの手法を用いても見た目には大きく変わらないですが、background-imageプロパティには<img>要素に比べ以下のような差異があります。

  • background-imageプロパティは遅延読み込みされない
  • スクリーンリーダーによるテキスト読み上げなどができない

background-imageプロパティは読み込み優先度こそ低いですが初期読み込みには含まれます。そのためbackground-imageプロパティに重たい画像を指定しているとその分読み込み完了まで時間がかかります。

<img>要素でいうところのalt属性にあたるものがbackground-imageプロパティにはありません。そのためページ上意味ある画像(例:説明文が書かれた画像)を使うとスクリーンリーダー使用者にはその意味が伝わらず、内容の理解を妨げてしまう可能性があります。

background-imageプロパティと<img>要素の使い分けを考えるときには、実装の手間だけでなく、その画像のページ上の意味とファイルサイズも考慮にいれておくと良いでしょう。

まとめ

パフォーマンスチューニングの中でも比較的インパクトの大きい画像の読み込みについて解説しました。ページ内で使われている画像が多くなればこれらの削減の効果も大きくなってきます。もしサイトの表示スピードが遅いと感じたら、改善の第一歩として、画像の読み込みについて見直してみると良いかもしれません。

223
105
464