ピュアJSで画像の遅延読み込み

Webページに大量に画像を貼るとページの読み込みが遅くなる場合があります。そのような時は、Webページ側で画像を遅延読み込み(レイジーロード = lazyload)させることで快適なページにすることができます。遅延読み込みの方法はいくつかありますが、例えば、ユーザがブラウザのタブウィンドウをスクロールした時にタブの表示領域に入ってきた画像要素だけを読み込みさせることでWebページ全体の表示を軽くする方法が一般的だと思います。

今回はそのような画像の遅延読み込みをピュアなJavaScriptだけを使って実現してみましょう。

目次

サンプルコード

...
<div style="height: 100vh;"><!-- テストのため画像間にわざと大きな隙間を作成 -->&nbsp;</div>
<img data-src="https://imgur.com/X4UOnBj.jpg" />
<div style="height: 100vh;"><!-- テストのため画像間にわざと大きな隙間を作成 -->&nbsp;</div>
<img data-src="https://imgur.com/JgzcbgD.jpg" />
<div style="height: 100vh;"><!-- テストのため画像間にわざと大きな隙間を作成 -->&nbsp;</div>
<img data-src="https://imgur.com/A4hfUVC.jpg" />

<script type="text/javascript">
/**
 * 画像遅延読み込みハンドラ
 * @param {Event} e
 *
 * タブの表示領域(viewport)に入ってきた全ての画像要素 img[data-src] を取得し、
 * data-src 属性値を src 属性にセットし画像をロード。
 * 画像ロード後は data-src 属性自体を削除します。
 * 
 * NOTE: 全ての画像 img[data-src] がロード完了したら、
 * この関数自体を全ての関連イベントリスナーから削除します。
 */
function loadImageHandler(e) {
  const els = document.querySelectorAll('img[data-src]');
  for (let len = els.length, i = 0; i < len; i++) {
    const img = els[i];
    if (isElementInViewport(img)) {
      const src = img.getAttribute('data-src');
      img.setAttribute('src', src);
      img.onload = function() {
        // console.log("画像 %s をロードしました。", src);
        img.removeAttribute('data-src');
      };
    }
  }
  if (els.length <= 0) {
    // console.log("全ての画像を読み込んだのでイベントリスナを削除");
    removeLoadImageHandler();
  }
}

/** 指定された要素がViewportに入ったかどうかを返す */
function isElementInViewport(el) {
    // NOTE: jquery要素にも対応:
  if (typeof jQuery === "function" && el instanceof jQuery) {
    el = el[0];
  }
  let top = el.offsetTop;
  let left = el.offsetLeft;
  let width = el.offsetWidth;
  let height = el.offsetHeight;
  while (el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }
  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}

/**
 * 画像遅延読み込みハンドラを全ての関連イベントリスナーに追加
 */
function addLoadImageHandler() {
  window.addEventListener('scroll', loadImageHandler);
  window.addEventListener('resize', loadImageHandler);
  window.addEventListener('load', loadImageHandler);
  window.addEventListener('DOMContentLoaded', loadImageHandler);
}

/**
 * 画像遅延読み込みハンドラを全ての関連イベントリスナーから削除
 */
function removeLoadImageHandler() {
  window.removeEventListener('scroll', loadImageHandler);
  window.removeEventListener('resize', loadImageHandler);
  window.removeEventListener('load', loadImageHandler);
  window.removeEventListener('DOMContentLoaded', loadImageHandler);
}

// 全ての関連イベントリスナーにloadImageHandler関数をセット
addLoadImageHandler();
</script>
...

サンプルコードレビュー

サンプルとして3つの画像を配置しました。それぞれの画像の隙間にはスクロールを発生させるために無駄な空間が設けられています。

まず最初にaddLoadImageHandler()が実行され、全ての関連するイベントリスナー(例えばscrollイベント等)に画像遅延読み込みハンドラloadImageHandler関数をセットしています。このloadImageHandler関数は、ユーザがタブをスクロールしたタイミング等に呼び出されます。

loadImageHandler関数内では、 data-src 属性を持つ画像要素だけを抽出し、それらの画像要素がタブの表示領域に入っている場合だけ画像をロードするといった処理を行っています。それぞれの画像要素の画像がロード完了した後は data-src 属性を削除して次回の抽出から弾くようにしています。

Webページ上のdata-src属性を持つ全ての画像要素の画像読み込みが完了したら、最終的にremoveLoadImageHander()を実行し、関連するイベントリスナーから画像遅延読み込みハンドラ関数を削除しています。

サンプルコードを実行

先ほどのサンプルコードを実行しています。 画面を下にスクロールしていくと一つづつ画像が遅延読み込みされます↓

 
 
 

以上です。

オススメ:Zattoyomiで時事ネタチェックの時間節約!
芽萌丸プログラミング部 @programming
プログラミング関連アカウント。Web標準技術を中心に書いていきます。フロントエンドからサーバサイドまで JavaScript だけで済ませたい人たちの集いです。記事は主に @TanakaSoftwareLab が担当。