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

サイト内の<imgタグで掲載している画像は、ブラウザがページにアクセスし、HTMLソースを読み込んだ時点で全てダウンロードされます。

例えば、このページでは4つの画像を使用していますが、Chrome DevToolsの「パフォーマンス」や「ネットワーク」の箇所で確認すると、ページにアクセスした直後に全ての画像がダウンロードされていることが分かります。

全ての画像がダウンロードされた表示

そのため、ページ内で画像を大量に掲載している場合、転送量の増大やリクエスト回数が多くなることでサイトの表示が重くなります。また、訪問者がすぐに離脱すると、せっかく読み込んだ画像のデータ転送が無駄になってしまいます。

このような場合、画像の<imgタグに、loading="lazy"を指定すると、画面をスクロールして実際に表示される時まで読み込みを遅延させることができます。

<img src="/sample.jpg" alt="" loading="lazy">コピーボタンコピーチェックボタン

例えば、このページの4つの画像全てにloading="lazy"を設定した場合、すぐに使用するファーストビューの2つの画像を除いて、ページ中段以降の画像はダウンロードされなくなります。

loading="lazy"を設定した場合

このloading="lazy"属性により、実際にブラウザが画像を表示する時にしかダウンロードされなくなるため、不必要なデータの転送やリクエストが抑制され、サイト表示の高速化につながる可能性があります。

ただし、素早いスクロールでの表示にも対応するため、余裕をもって事前に読み込む遊びの部分があります。

ファーストビュー付近の画像については、loading="lazy"を設定していても、上記のようにアクセス直後にすぐにダウンロードされるため、ページの中段以降で大量に画像を使用しているケースでないと、高速化の効果はそれほど見込めないかもしれません。

loading属性に「lazy」を設定して遅延読み込みに対応

loading="lazy"属性は、 <imgタグや<iframeタグに追加することで、リソースの読み込みを遅延できますが、<divタグなどについては使用できません。そのため、<imgタグでの画像や<iframeでの埋め込み動画の遅延読み込みに使用することが多いと思います。

  • loading="eager" → リソースをすぐにダウンロード。デフォルトの状態。
  • loading="lazy" → 実際に使用するときまで、リソースの取得を延期。

loading属性の値には、「eager」と「lazy」がありますが、何も設定しないデフォルトの値は「eager」となっており、この場合には自動的にすぐにダウンロードが開始されます。

これをloading="lazy"に設定することで、実際に使用するまで、ダウンロードを停止させることができます。

ただし、ファーストビュー付近の画像については、ページにアクセスした直後に使用するため、たとえloading="lazy"を設定していても、すぐにダウンロードされます。そのため、ファーストビューの付近では、loading="lazy"を設定せず、多少スクロールするあたりから設定しておくとよいでしょう。

また、可能であれば、ファーストビューでlazyを設定しない画像については、<head>タグ内にプリロードを設定しておくとよいかもしれません。

<link rel="preload" href="/example.jpg" as="image">コピーボタンコピーチェックボタン

このプリロードタグは、loading="lazy"の遅延読み込みとは関係ありませんが、設定することで、優先的にダウンロードされることになります。

加えて、ファーストビューの画像に関しては、あらかじめwidthとheightを設定しておくと、レンダリングの際の計算がはやくなり、ページの表示が速くなる可能性があります。

Lazy画像が表示されるまでの遊びの範囲

ブラウザは画像の表示に遅れが生じないよう、一定の遊びの部分を確保し、事前に余裕をもって画像の読み込みを開始します。

そのため、サイトにアクセスした直後は、ファーストビューの画像のみではなく、この遊びの部分までを含めた範囲までダウンロードされるため、この範囲内にはloading="lazy"属性を設定しないことをおすすめします。

具体的に、この遊びの部分が何pxなのかを当サイト運営者が確認してみたところ、デスクトップの場合ですと軽めに2スクロールをする程度の距離と感じています。

例えば、以下のように、loading="lazy"を設定した幅500px、高さ100pxの画像を、lazy1~lazy30まで縦に30個並べたとします。

デスクトップ端末の場合

Lazy loadingマージンのテスト

100pxが30個で高さ3,000pxになりますが、ページにアクセスした直後、ファーストビューの位置を超えてLazy18までダウンロードされました。

ファーストビューでダウンロードされた位置

そのため、遊びの部分がけっこうあると感じていますが、当サイト内の場合ですと、コンテンツの上部から1,800pxを超えたあたりからloading="lazy"を設定しないと遅延読み込みは機能せず、ダウンロードされてしまうようです。

コンテンツ部分の高さを600px程度としますと、画面の下限から1200px以上を超えたあたりからでないとloading="lazy"を設定しても意味がないものと思われます。

ただし、Living Standardのガイドラインによると、スクロール速度や勢い、ネットワークの品質などによって、この遊びのマージンの値を調節するように提案されています。ブラウザ開発者が、どの程度まで実装しているのかは不明ですが、デスクトップでは値が小さかったとしても、スマホなどの端末では値がさらに大きい可能性はあります。

例えば、Chromeの場合、2Gや3G、4Gなどで切り分けがされています。

"third_party/blink/renderer/core/html/lazy_load_image_observer.cc"

回線速度による切り分け

回線速度が速いほど、遊びの部分が小さくなっています。

"third_party/blink/renderer/core/frame/settings.json5"

Lazy loadingの閾値

デスクトップの記載がありませんでしたが、上記のとおり、おそらく1250pxあたりになるもしれません。

loading="lazy"属性は、画像メインのコンテンツで使用すると効果的かと思いますが、一般的なサイトでは、ページ内の画像は数枚程度のことが多いと思いますので、それほど大きな効果は期待できないかもしれません。

JavaScriptを使用して画像の遅延読み込みをする方法

この画像の遅延読み込みについては、インターセクションオブザーバーを利用したJavaScriptで対応することもできます。

このインターセクションオブザーバーは、ページ内の特定の要素が画面の表示領域と交差するタイミングを監視し、その要素が表示された際に、指定した処理を実行するためのものです。

loading="lazy"属性は、ブラウザにデフォルトで実装されている機能であるのに対し、インターセクションオブザーバーはJavaScript APIである点で違いがあります。

例えば、事前にsrc属性をdata-sourceなどのカスタムデータ属性(data-*)にして<imgタグとして機能しない状態にしておき、ページにアクセスした直後は画像をダウンロードしないようにしておきます。

また、遅延読み込みをしない画像と区別するため、delayedなどのクラス属性も指定しておきます。

<imgタグのサンプル

<img data-source="/sample.jpg" alt="" class="delayed">コピーボタンコピーチェックボタン

JavaScriptのサンプル

document.addEventListener("DOMContentLoaded", function() {
  const delayedImages = [].slice.call(document.querySelectorAll("img.delayed"));
  if ("IntersectionObserver" in window) {
    let imageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let delayedImage = entry.target;
          delayedImage.src = delayedImage.dataset.source;
          delayedImage.classList.remove("delayed");
          imageObserver.unobserve(delayedImage);
        }
      });
    });
    delayedImages.forEach(function(delayedImage) {
      imageObserver.observe(delayedImage);
    });
  } else {
    delayedImages.forEach(function(delayedImage) {
      delayedImage.src = delayedImage.dataset.source;
      delayedImage.classList.remove("delayed");
    });
  }
});コピーボタンコピーチェックボタン

その後、画像と表示領域が交差するタイミングで、data-source属性に格納された画像URLのデータをsrc属性に戻すことで、画像を遅延して読み込む仕組みになります。

また、オプションでマージンの値を指定することにより、上記の遊びの部分をタイトにし、ギリギリのマージンに変更することができます。この値については、以下のrootMargin: '10px 100px 10px 100px'の箇所にて、CSSと同様に上右下左で値を指定できます。

document.addEventListener("DOMContentLoaded", function() {
  const delayedImages = [].slice.call(document.querySelectorAll("img.delayed"));
  if ("IntersectionObserver" in window) {
    const options = {
      rootMargin: '10px 100px 10px 100px'
     };
    let imageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let delayedImage = entry.target;
          delayedImage.src = delayedImage.dataset.source;
          delayedImage.classList.remove("delayed");
          imageObserver.unobserve(delayedImage);
        }
      });
    }, options); 
    delayedImages.forEach(function(delayedImage) {
      imageObserver.observe(delayedImage);
    });
  } else {
    delayedImages.forEach(function(delayedImage) {
      delayedImage.src = delayedImage.dataset.source;
      delayedImage.classList.remove("delayed");
    });
  }
});コピーボタンコピーチェックボタン

上記のloading="lazy"の場合、1250px程度の遊びの部分があり、使いにくい面もありますが、こちらのJavaScriptの場合には、10px程度でも指定できるメリットがあります。

ただし、src属性をdata-source属性にすることで、HTML Validatorではエラーがでますし、JavaScriptのコード自体のリクエストや読み込みも発生します。また、JavaScriptをオフにしているユーザーの場合、遅延表示が機能しないだけでなく、画像そのものが表示されなくなる懸念もあります。

このインターセクションオブザーバーは、コンテンツが無限に表示されるインフィニットスクロールなどでも使用されていますが、画像の遅延読み込みについては、上記のloading="lazy"属性を使用した方がシンプルで簡単かと思います。