スクロールイベントを使って画像の遅延読み込みをする時代は終わった……のか?

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
最近全然電車乗ってないなあ……都内在住なのに

この記事を三行にまとめると

スクロールイベントを使わない遅延読み込み
こっちを使う方が良さげな気はします
細かい制御はできなくてもそんなに困らないんじゃないかなあ
ウェブページの表示速度の改善や通信量を削減する方法としてよく使われる方法に画像の遅延読み込みというのがあります。

遅延読み込みとはページを開いた時にファーストビューに含まれていない部分(最初に見えない部分)の画像は最初は読み込まないようにしておいて、画面がスクロールしてそろそろ画面に現れそうだなってタイミングで画像を読み込むというものです。これによりページを開いた直後の画像の読み込みが行われないので、画像の分の通信量を減らすことができてページの読み込み速度も上げることができます。画像の少ないページではあまり意味はないですが、大量の画像を表示するページではだいぶ速度に差が出ると思います。

この遅延読み込みを実装する方法はいくつかあるのですが、おそらく最も一般的なのはスクロールイベントを使って読み込むという方法だと思います。



スクロールイベントを使った遅延読み込み

これはjavascriptで画面のスクロールを検知して現在位置を取得し、この現在位置がある程度画像に近くなったらimgタグのsrc属性に目的の画像のURLを入れるというものです。

コードの一例を書くとこんな感じです。

//html
<img src="dot.gif" data-src="画像のURL" class="lazyload" />
<img src="dot.gif" data-src="画像のURL" class="lazyload" />
<img src="dot.gif" data-src="画像のURL" class="lazyload" />
<img src="dot.gif" data-src="画像のURL" class="lazyload" />
<img src="dot.gif" data-src="画像のURL" class="lazyload" />

//javascript
document.addEventListener("DOMContentLoaded", function() {
  let t, id;
  t = function () {
    l = document.querySelectorAll("img.lazyload");
    if (l.length == 0) {
      window.removeEventListener("scroll", s);
      return;
    }

    for (var d = l, g = d.length; g--;) {
      let f = window.innerHeight,
      h = d[g].getBoundingClientRect(),
      b = h.top - f,
      c = h.bottom;
    
      if (b <= 400 && -400 <= c) {
        d[g].src = d[g].getAttribute("data-src");
        d[g].addEventListener("load", function () {
          this.classList.remove("lazyload");
        })
      }
    }

    id = 0;
  },

  s = function () {
    if (id) {
      return;
    }
    id = setTimeout(t, 500);
  },

  window.addEventListener("scroll", s), t();
});

これは実際にこのサイトで前に使っていた遅延読み込みのコードです。今回はこのコードはあまり重要じゃないのでざっくりとしか説明しませんが、スクロールするたびにlazyloadというクラスのついたimgタグの現在の位置を読み込み、画面の端っこと画像の端っこの差が400px以内にあるimgタグのsrc属性をdata-src属性に入れておいたURLに置き換えるという処理を行っています。src属性に入っているdot.gifは適当に用意したちっちゃくて軽い代替画像だと思ってください。

400pxという値も適当です。だいたいこれくらいなら通常のスクロールスピードに対応できるだろうという数値を入れています。この値が小さすぎると画像の置き換えが完了する前に画面に現れてきてしまうことがあります。これに関しては正解の値とかはないので、想定されるスクロールのスピードと画像の重さから何となく良い感じの数値を設定することになります。



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

とりあえず上記の感じで遅延読み込みを実装することはできるのですが、最近ではスクロールイベントを使わずに「Intersection Observer API」というjavascriptのAPIを使った遅延読み込みも推奨されているようです。

Intersection Observer API

これは「交差監視API」と呼ばれるもので、簡単に言えば対象の要素が画面に現れる瞬間を監視してくれるAPIです。これを使えばスクロールによって画像が画面の端っこと交差する瞬間に何らかの動きを実装することができるので、上記と同じような遅延読み込みを実装することができます。

まあ言葉で長々説明してもあれなんで、早速上記と似た動きになるコードを書いてみましょう。

document.addEventListener("DOMContentLoaded", function() {
  imageobserver = new IntersectionObserver(function(entries) {
    entries.forEach(function(entry) {
      if(entry.isIntersecting) {
        let image = entry.target;
        image.src = image.dataset.src;
        imageobserver.unobserve(image);
      }
    });
  });

  let images = [].slice.call(document.querySelectorAll('.lazyload'));
  images.forEach(function(image) {
    imageobserver.observe(image);
  });
});

こんな感じです。imagesという変数にlazyloadクラスのついたimgタグの配列を入れて、foreachでそれぞれのimgタグに対して交差監視APIを有効にしています。

ページの上部からスクロールしてきた場合、画面の下端とimgタグの上端が重なる(交差する)と「isIntersecting」がtrueになるので、この時にsrc属性にdata-src属性のURLをセットすればスクロールイベントの時と同じような遅延読み込みの動きになります。

一度入れ替え終わった画像に関してはそれ以降は監視する必要がないので「imageobserver.unobserve(image)」でAPIを無効にしています。

こっちもだいぶざっくりした説明になってしまいましたが……このAPIについて詳しく知りたい方は上記のリンクからドキュメントを見てみてください。他にもいろいろ取得できる情報はあるので、遅延読み込み以外のイベントにも使えるかもしれません。






スクロールイベントとどっちが分かりやすいかは個人差があるかもしれませんが、常にスクロールイベントを発動させて処理を行うよりも交差監視APIで特定のタイミングだけイベントが発動する方がページの負荷は下がるので、こっちを使う方が良さげな気はします。コードもAPIの方がすっきりしている気がするし。

しいて言えば「あと何ピクセル画面に近づいたら画像を読み込む」みたいなことはこっちのAPIではできないみたいなので、それができないと困るという場合にはスクロールイベントでの実装が必要になりますが……個人的には画像の遅延読み込みに関してピクセル単位での細かい制御はできなくてもそんなに困らないんじゃないかなあと思います。
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください