特定タグのリソースを遅延読み込みさせるスクリプト

2021/08/13
4
thumbnail

サイト高速化に欠かせないリソースの遅延読み込み。

今まではあちこちでネイティブLazyloadを使っていたんですが、ふとしたきっかけから新しい方式に切り替えることにしました。こちらのほうが遅延読み込みの機能として確実かなぁと思ったので。

そして何より、設定が簡単!

というわけで、今回作成した遅延読み込みスクリプトをご紹介します。外部ライブラリを一切使用せず、JavaScriptのみで実現したシンプルな遅延読み込み機能です。

ネイティブLazyloadが効いていない…?

きっかけはPageSpeed Insightsで測定していたときのことでした。

PSI-mobile1

…なんだか転送サイズが大きい??

画像は全部loding="lazy"でネイティブLazyloadしているはずなのに、遅延読み込みされていないもよう。開発者タブの「Network」で見てみたら確かに遅れて読み込まれているっぽいんですが、微妙に気になります。

JavaScriptで遅延読み込みさせたら結果変わるのかな?と試してみたところ…

PSI-mobile2

なんと、半分未満になりました!

これはなかなか衝撃の結果です。どちらもモバイルの結果ですが、100KBの差はかなり大きいのではないでしょうか。

PSIの結果画面では「スコアに直接影響しない項目」ということになっていますが、ファイルサイズが小さいほうが早く読み込まれるので、結果的にページの表示速度には影響すると思うんですよね。

ちなみに、ネイティブLazyloadのサポート状況を「Can I use」で改めて確認してみたところ、IEはもとよりSafariもアウトだったみたいです。最新バージョンのFirefoxでも部分的サポートという状況。※記事執筆時点

lazyload-support

普及率が74.7%ということなので、だいたいはOKですがまだ完全に頼り切ってしまうわけにはいかなさそうですね。

ソースコード

導入は、HTML編集で</body>の真上に以下のコードをペタッと貼り付ければOKです!

<script>
//<![CDATA[
(function(window, document) {
  // 遅延読み込み
  window.addEventListener('DOMContentLoaded', lazyLoad);
  window.addEventListener('scroll', () => {
    throttle(lazyLoad(), 100);
  });
    
  function lazyLoad() {
    const elms = document.querySelectorAll('img, iframe');
    for (let i = 0; i < elms.length; i++) {
      if ((elms[i].getBoundingClientRect().top - window.innerHeight < 0) && elms[i].getAttribute('data-src')) {
        elms[i].setAttribute('src', elms[i].getAttribute('data-src'));
        elms[i].removeAttribute('data-src');
      }
    }
  }
  
  function throttle(fn, wait) {
    let time = Date.now();
    return function() {
      if ((time + wait) < Date.now()) {
        fn();
        time = Date.now();
      }
    }
  }
})(window, document);
//]]>
</script>

デフォルトでは対象となるimg, iframeタグをすべて取得し、「すでに表示領域内にあり、かつdata-srcにデータが入っているもの」に対してdata-srcのデータをsrcに移しかえるようにしています。

実行タイミングはDOM構築時とスクロール時です。ユーザエクスペリエンス向上のため、DOM構築時で表示領域内にあるものはその時点で表示させます。あとはスクロール時に順番に表示。

スクロールの場合は、念のためthrottleで間引き処理をしています(100ミリ秒おきに実行)。正直なくても違いはよく分かりませんが、多少は負荷軽減に役立っているはず?

実行タイミングに関する補足

デフォルトでは、最初の遅延読み込み処理はDOM構築時に実行されます。これはフィードなどを利用したウィジェット(最新記事や関連記事)の画像URLがdata-srcにセットされていた場合、すぐに実行するとタイミングが早すぎて画像が永遠に読み込まれないという事態が発生するためです。

ただし、そうした心配がない(フィード処理などタイムラグを生じる要因がない)場合、すぐに実行したほうが画像が早く表示されるので、速度や見た目を気にされる方はコードを以下のように置き換えることを推奨します。

window.addEventListener('DOMContentLoaded', lazyLoad);

↓↓

lazyLoad();

この場合、スクリプトをできるだけ</body>に近い後ろのほうに置いてください。

参考:JSの実行するタイミング色々 - Qiita

使い方

使い方はとっても簡単。遅延読み込みさせたいリソースをdata-srcにセットするだけです。

例:

<img alt="" width="200" height="200" data-src="picture.jpg"/>

srcと両方セットされている場合は、data-srcの内容が優先されます。

推奨事項として、imgタグなどのwidth, heightはできるだけ指定するようにしてください。指定がなくても問題はないですが、遅延読み込みされる際に表示が一瞬崩れてしまう可能性があります。

遅延読み込みさせるタグを増やしたいときは、次のようにカンマ区切りでタグを追加していってください。

const elms = document.querySelectorAll('img, iframe');

↓↓

const elms = document.querySelectorAll('img, iframe, video, audio');

遅延読み込みと脱WebP化

遅延読み込みだけでこれだけ転送サイズを削減できるのだから、WebP画像もあわせて使えばもっと削減できるのでは!?とウキウキしていました。

が、実際にpictureタグで複数ブラウザ対応した形でWebP画像をセットし、内側のsourceタグにも遅延読み込みを適用してみても、サイズが減るどころか増えてしまいました(泣)

おそらく、すでに表示されているものに関してimgとsource両方とも読み込まれているのが原因のようですね。WebP画像単体であれば間違いなく削減できると思うんですが、惜しいところです…

というわけで、今回を機に脱WebP化しました。WebPの恩恵を受けられないのは残念ですが、転送サイズは大幅に減るので良しとします。

4件のコメント
こんばんは。
私もネイティブLazyload に少し懐疑的になってきていましたので、こちらの遅延読込スクリプトを導入させて頂きました。ちなみに脱WebPはしていません。記事内の画像への対応はこれからボチボチとやろうかなと(^^;

これまでのネイティブLazyLoad は見た目では全然遅延読込みされてるような気がしてなかったんですが、このスクリプトを導入したら、適切に処理されているということが明確に分かるようになりました。PSIでの評価も上がってるようです。
いつもいつも有用なツールの提供、ほんとにありがとうございますm(_ _)m
Safari は Mac の場合 [開発] - [実験的な機能] で
Lazy iframe loading と Lazy image loading の2つにチェックを入れます。

iOS の場合、設定にて [Safari] - [詳細] - [Experimental Features] で
上記2つの項目をオンにします。

macOS11 と iOS14 以降では、少なくとも上記2つの項目が選べるはずです。
それ以前は、実施してないので わかりません。
>ふじやんさん
こんにちは。
スクリプトがちゃんと役に立っているか不安だったのですが、導入していただけてとても嬉しいです。機能も問題なく順調に動作しているようで安心しました。

こちらこそ、ご丁寧に報告いただきありがとうございます。励みになります(*^_^*)
>アタルさん
なるほど、Safariは実験的な機能として遅延読み込みをサポートしていたんですね。知りませんでした…

情報提供ありがとうございます!