JS memo

お洒落にJavaScript。

2019年5月28日21:08

IntersectionObserverでユーザーにもブラウザにも優しいサイトの作成

メインビジュアル

IntersectionObserverでユーザーにもブラウザにも優しいサイトの作成

いきなりですが皆さんはスマホを使っていて速度制限になった経験はありますか?
私はsoftbankのギガモンスターを契約していて、家にwifiを完備しているにも関わらず、毎月速度制限にならなかったことがないです。

私の場合ほとんどyoutubeにギガを奪われていますが、サイトを閲覧した時にも画像や動画の読み込みでデータは消費されています。
しかもJSで何も実装していない場合、画面に見えていないページ下部のコンテンツにもギガは奪われているのです!悪い言い方をするとデータの無駄遣いですね。
そこで今回はユーザーの通信量を少しでも守るべく、JavaScriptのIntersectionObserverを実装したいと思います。

目次

  1. IntersectionObserverとは
  2. scrollイベントじゃダメなの?
  3. IntersectionObserverで画像を遅延読込してみた
  4. Polyfillで非対応のブラウザ処理
  5. まとめ

IntersectionObserverとは

抽象的かつ擬人法でいうとスマホやPCで現在表示されている画面の位置をずっと監視している警備員さんみたいなもの。もちろん24時間体制。
警備員さんはすごく良い人で「画面上にこのコンテンツが現れたらこうゆう動きをしてほしい!」みたいなお願いを聞いてくれます。

例えば、画像を表示したい位置に設置しておいて、警備員さんに「設定した画像の位置までスクロールされたら画像を読み込んでね」とお願いしておきます。
そうすることでもしユーザーがIntersectionObserverを実装した画像までスクロールしなかった場合、その画像分のデータ消費を防ぐことができます。

上記の例のようにIntersectionObserverは画像の遅延読み込みや動画の自動再生などによく利用されています。
IntersectionObserverのメリットをまとめておきます。

1. サイトの表示速度改善

IntersectionObserverを利用してコンテンツの遅延読み込みを行うことで、最初に表示されるページの速度を改善することができます。
サイトを表示させるにはザックリととLoading→Scripting→Rendering→Paintingという段階があります。
このフローの最初「Loading」はページに使用するhtmlやcss、画像ファイルをサーバーに取りに行くフローで、 ここでIntersectionObserverで指定されたファイルの読み込みを後回しにすることで、その分時間を短縮できるということです。

2. データの無駄遣いを減らす

上記でも例をあげたように、IntersectionObserverで指定されたコンテンツはユーザーの画面に表示されないのでその分のデータを節約できます。

IntersectionObserverはメリット多めの優れたAPIですが、一つだけ大きなデメリットがあります。。。
ブラウザの互換性が低く、IE, iOSに対応していないことです。
まぁでも、すごく深刻に言って見たものの、この問題はw3cさんが作成してくれたintersection-observer.js(polyfill)を読み込むだけで解決できます。

scrollイベントじゃダメなの?

ここでJavaScriptを勉強している方なら「scrollイベントと何が違うの?むしろscrollの方がブラウザの互換性高いし、scrollでいいんじゃない?」と思うのではないでしょうか。
scrollイベントも画面の位置を警備員さんが監視してくれてるという面では似ていますが、scrollの警備員さんはちょっとお節介で、「今ユーザーはこの位置にいるよ!」ということを逐一伝えて来ます。

この厄介さを理解するために実際に以下のコードを実装して比べてみましょう。

<div id="contents">
  <h1>デモページ</h1>
  <!--デモでは画像と画像の合間にcssで空白を設定しています。-->
  <img data-lazy="./website/assets/images/main0_pc.jpg">
  <img data-lazy="./website/assets/images/main1_pc.jpg">
  <img data-lazy="./website/assets/images/main2_pc.jpg">
  <img data-lazy="./website/assets/images/main3_pc.jpg">
</div>
<script>
window.addEventListener("scroll", (event) =>{
  document.querySelectorAll("img").forEach(img =>{
    console.log("💩");
    const rect = img.getBoundingClientRect().top;
    if(rect <= window.innerHeight){
      const src = img.getAttribute('data-lazy');
      img.setAttribute('src', src);
    }
  })
});
</script>

お分かりいただけただろうか。ユーザーがスクロールするたびにイベントが発生してしまいます。
もしユーザーのスクロールが超高速だとScroll jackと言われるscrollイベントが追いつかなくて 画面が固まってしまう現象がおきてしまいます。つまりユーザーにもブラウザにも負担をかけてしまうのです。

なので早く実装しなければならない!ブラウザの互換性を気にする余裕がない!というとき以外はIntersectionObserverを利用するのがユーザーにもブラウザにもエコなのではないでしょうか。

実際にIntersectionObserverを実装してみた

実装するまでに長々と喋ってしまい、すみません。ではIntersectionObserverの警備員さんに画像遅延のお願いをする実装をしていきたいと思います。

1. 下準備<HTML>

まず遅延読み込みさせる画像のURLをdata-lazy属性の値に設定したimgタグをhtmlに書きます。
あとで実装するJavascriptでdata-lazyを取得してsrc属性に画像のURLを読み込ませます。

<img data-lazy="sample0.jpg">
<img data-lazy="sample1.jpg">
<img data-lazy="sample2.jpg">

次にJavascriptでIntersectionObserverを実装していきます。

2. 遅延させたい要素の取得<JavaScript>

まず遅延させたいイメージの要素を全て取得して、forEachで一つ一つに読み込みを遅延と監視をお願いする箱を作成します。

document.querySelectorAll("img").forEach(target => {
  //...
};

3. IntersectionObserverのインスタンス作成<JavaScript>

最初にIntersectionObserverのインスタンスを作成します。

document.querySelectorAll("img").forEach(target => {
  onst io = new IntersectionObserver((entries, observer)=>{
  //...
  });
};

IntersectionObserverのコンストラクターは二つの引数をもつ関数を定義することができます。

entries → 現在の画面上の情報を配列に格納したもの

observer → IntersectionObserverのインスタンス

4. 画像の遅延設定<JavaScript>

次に遅延読み込みする画像要素が画面上に現れた時の処理を設定します。
もし遅延させたい画像(target)が画面上に現れたらsrc属性を作成し、data-lazy属性の値(画像のURL)を取得して、src属性の値に代入します。
今回は設定したそれぞれの画像が一回表示されたら、「もう監視しなくていいよ!」という設定をobserver.disconnectでしています。

document.querySelectorAll("img").forEach(target => {
  const io = new IntersectionObserver((entries, observer)=>{
    entries.forEach( entry =>{
      console.log("💩");
      if(entry.isIntersecting){
        const img = entry.target;
        const src = img.getAttribute('data-lazy');
        img.setAttribute('src', src);
        observer.disconnect();
      }
    });
  });
};

5. 処理方法を設定した画像の監視

先ほど処理設定した画像要素が画面上に現れるかどうか警備員さんに監視をお願いします。

io.observe(target);

あとは遅延読み込みの設定をした画像が画面上に現れるまで待つだけ!

コード全体のhtml

<h1>デモページ</h1>
<!--html-->
<!--デモでは画像と画像の合間にcssで空白を設定しています。-->
<img data-lazy="sample0.jpg">
<img data-lazy="sample1.jpg">
<img data-lazy="sample2.jpg">
<img data-lazy="sample3.jpg">
<!--javascript-->
<script>
document.querySelectorAll("img").forEach(target => {
  const io = new IntersectionObserver((entries, observer)=>{
    entries.forEach( entry =>{
      console.log("💩");
      if(entry.isIntersecting){
        const img = entry.target;
        const src = img.getAttribute('data-lazy');
        img.setAttribute('src', src);
        observer.disconnect();
      }
    });
  });
  io.observe(target);
});
</script>

Polyfillで非対応のブラウザ処理

IntersectionObserverを実装するにあたり、一つ注意して欲しいことはIEやiOSなど主要なブラウザで対応されていないことです。
そのため下記のスクリプトを実装するページに読み込ませてあげてください

<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>

まとめ

ここまで読んでくれてありがとうございました&お疲れ様でした。
IntersectionObserverの素晴らしさを理解していただけましたでしょうか。
冒頭でユーザーの通信料のために!と言っていましたが、IntersectionObserverに注目しているもう一つの理由がありまして、 2019年5月7日にWeb mastersチームが掲載した記事に「googlebotがパワーアップしてIntersectionObserverを理解できるようになります!」と名言されていたことです。

googlebotのパワーアップについてはコチラをご覧ください。

ユーザーにもブラウザにも優しい、そしてついにgooglebotがpolyfillなしで理解できるようになるかも?なIntersectionObserverのAPIを使ってみてください。