Web

Intersection Observer API を使った画像の遅延読み込み

投稿日:2019年3月29日 更新日:

1. 画像の遅延読み込み(遅延ローディング)

画像を載せているウェブページがあったとします。

ブラウザがそのウェブページにアクセスすると、普通はそのページ(HTMLなど)と一緒にそこにある画像もダウンロードして画面上に表示します。

しかし「(画像の)遅延読み込み」を行うと、ウェブページアクセス時には画像をダウンロードせず、後で必要になったタイミングで「ダウンロード」+「表示」を行います(いろいろなタイミングがあると思います)。これによりページアクセス時の「読み込み中」時間が短くなり、レンダリング(画面描画)開始タイミングが速くなるので、ユーザーのストレスが減ります。そもそもページの上の方にある画像ならともかく、下の方にある画像であれば最初は画面に表示されませんので、ページアクセス時に読み込む必要はありません。

Chrome は バージョン76 から loading 属性に対応しています。従って本記事で説明しているJavaScriptの処理は必要ありません。

追記 2019-08-23」を参照してください。

2. Intersection Observer API

ということで「必要になった時にはじめて画像がダウンロードされ表示される」という動作が求められるのですが、このような処理を実現するために用意されているのが Intersection Observer という API です。

この API は、「ある画面上の要素が、ブラウザ上の表示領域(ビューポート, viewport)に対してどの位置にあるか? を観察して教えてくれる」という機能を持っています。

遅延読み込みを実現するという観点から少し言い換えると、

ユーザーがブラウザ上のページをスクロールした際、特定の画像要素(imgタグ)が視界に入ったことを検知することができる

ということが可能になります。

仕様

3. Intersection Observer API を使って画像を遅延読み込みする例

Intersection Observer API を使って画像を遅延読み込みするサンプルコードを紹介します。 細かい部分については説明を省きますが、おおよその動きが分かればこのコードを利用して自分のウェブページでも利用することができると思います(あくまで一例ですが)。

1. HTML

画像を表す <img> タグを以下のように書きます。

(1) srcset を使わない場合

<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7"
  width="300" height="300" alt=""
  class="lazyload"
  data-src="foo.jpg" >
  • src属性には、dataスキームを使って「1×1ピクセルの透過GIF」を表す文字列を指定します。ページアクセス時には、このダミー画像が使用されるようにしておきます。
  • data-src属性に、実際の画像ファイルへのパスを指定します。この画像の表示位置が画面上に入った時点で、この値を src属性にコピーするようにします。
  • class属性として “lazyload” というクラス名をセットし、JavaScript コードからこの要素を取得するための目印とします。

(2) srcset を使う場合

<img src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7"
  width="600" height="300" alt=""
  class="lazyload"
  data-src="dummy.png"
  data-srcset="dummy.png 600w,
               dummy.300x150.png 300w"
  sizes="(max-width: 600px) 100vw, 600px">
  • 同じく src属性には、ダミー画像をセットします。
  • data-src属性については先程と同じですが、これに加えて srcset属性にコピーされる元の値を data-srcset属性に指定しておきます。
  • こちらも class属性として “lazyload” というクラス名をセットします。

2. JavaScript

コードの意味については、コメントに書いておきました。

document.addEventListener("DOMContentLoaded", function() {
  // 処理の対象とする画像要素を取得します
  let lazyImages = [].slice.call(document.querySelectorAll("img.lazyload"));
  // IntersectionObserver に対応しているブラウザのみ、処理を実行します
  if ("IntersectionObserver" in window) {
    // コールバック関数を登録して、IntersectionObserver のオブジェクトを生成します
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      // 対象となる画像要素毎に処理を行います
      entries.forEach(function(entry) {
        // この要素が画面に入ってきた場合
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          if (lazyImage.dataset.hasOwnProperty('src')) {
            // data-src 属性値があれば、src 属性にコピーします
            lazyImage.src = lazyImage.dataset.src;
            // dataset の src は削除します
            lazyImage.dataset.src = '';
            delete lazyImage.dataset.src;
          }
          if (lazyImage.dataset.hasOwnProperty('srcset')) {
            // data-srcset 属性値があれば、srcset 属性にコピーします
            lazyImage.srcset = lazyImage.dataset.srcset;
            // dataset の srcset は削除します
            lazyImage.dataset.srcset = '';
            delete lazyImage.dataset.srcset;
          }
          // この時点で画像が表示されるはずです

          //console.log("lazy loading!"); // チェック用
          // "lazy" というCSSクラスを削除します
          lazyImage.classList.remove("lazyload");
          // この画像要素を観察対象から外します
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });
    lazyImages.forEach(function(lazyImage) {
      // 観察を開始します
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // ここには、IntersectionObserver に対応していないブラウザ用の処理を
    // 記述しますが、Polyfill を使えばここの処理は必要ないでしょう
  }
});

※ このコードは、イメージと動画の遅延読み込み  |  Web Fundamentals  |  Google Developers を元にしています。
※ IE 11 でも動くように アローファンクション を使わずに書いています。

3. Polyfill

Can I use… を見ると分かるのですが、一部のブラウザでは Intersection Observer が未実装なので、それらのブラウザにも対応する場合は、Polyfill を導入します。

この中で実際にダウンロードして使用する JavaScriptファイルのリンクは、intersection-observer.js になります。

以下のようにこの JavaScript ファイルを読み込ませます。

<script src="path/to/intersection-observer.js"></script>

4. デモページ

デモページを用意しました。

5. iframe の遅延読み込み

iframesrc 属性で指定したリソースも、同じ要領で遅延読み込みできます。

例えば、以下は YouTube の埋め込みHTMLです。

<iframe width="560" height="315"
  src="https://www.youtube.com/embed/X8rE0q50VH8"
  frameborder="0"
  allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
  allowfullscreen></iframe>

これを遅延読み込みさせるには、以下の修正を行います。

  1. src の値を data-src にコピーし、src にはdataスキームを使ったダミー画像を指定します。
  2. class 属性もセットします。
<iframe width="560" height="315"
  src="data:image/gif;base64,R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7"
  data-src="https://www.youtube.com/embed/X8rE0q50VH8"
  class="lazyload"
  frameborder="0"
  allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
  allowfullscreen></iframe>

あとは JavaScriptコードで、セレクタ「iframe.lazyload」に合致する要素を取得して、同じような処理をするだけです。

6. lazyload 属性

<img><iframe>lazyload という属性を追加して、HTMLの仕様として遅延読み込みを可能にする」という内容の提案がされているようですが、普及するまでにはもう少し時間が掛かりそうです。

参考

追記 2019-08-23

Chrome の最新バージョン76 から loading 属性に対応しました。

例えば <img> タグの場合、以下のようにすれば自動的に遅延読み込みされます。

  • loading 属性に、lazy を指定する(loading="lazy")。
  • 高さと幅を指定する(height, width 属性を指定する、もしくは style 属性で heightwidth をセットする )。

実際に試してみたところ、ビューポート(ページ上の見えている部分)に画像が入る直前で読み込まれるのではなく、割と下の方の画像まで早めに読み込むようです。

参考

7. おわりに

Intersection Observer API を使って画像を遅延読み込みさせると、ウェブページにアクセスした時の表示スピードが大幅にアップします。

これはユーザーにとって使いやすいだけでなく、Google 検索エンジンからの評価も高くなります。

導入方法もそれ程難しいわけではありませんので、これからのウェブページは Intersection Observer API を使った画像の遅延読み込みが当たり前になっていくかもしれません。(シンプルなページならそんなことしなくていいと思いますが)

※ 但し、この場合 JavaScript の処理が実行されないと画像が表示されなくなり、HTML のみではページとして成立しなくなります。これはこれで不完全な気がします。ですので、JavaScript の処理がない場合は自動的に画像が表示されるような動作が仕様として規定されるのが一番望ましいのではないでしょうか。

Web Programming

Puppeteer を使った画像遅延読み込みテストを試してみました

2019.02.14
Web Programming

IntersectionObserver API を使ったテストページを作成しました

2019.02.13

参考

📂-Web

執筆者:labo


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

Web

TypeSquare の導入手順

Webフォントサービスである TypeSquare の導入手順について説明しています。

Web

URLリダイレクトの動作

URLリダイレクトの動きについて説明します。

Web

シンタックス・ハイライト・ライブラリ「highlight.js」の使い方

シンタックス・ハイライト・ライブラリ「highlight.js」の使い方について説明します。

Web

フォントサイズに関するCSSクラス名にはどんな名前があるのか調べてみました

目次はじめにCSS Fonts Module Level 3FontawesomeBootstrap (v4)まとめ参考 はじめに フォントサイズを表す CSSクラス名として、どんな名前を使うのがよい …

Web

<pre><code>タグが引き起こす モバイルユーザビリティのエラー「コンテンツの幅が画面の幅を超えています」

<pre><code>タグが、モバイルユーザビリティのエラー「コンテンツの幅が画面の幅を超えています」を引き起こしていました。