9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Intersection Observerを触ってみた

Last updated at Posted at 2017-05-25

#Intersection Observerとは
指定したDOM要素の交差点(Intersection)を監視するAPIです。

#Intersection Observerのメリット
従来、DOM要素の位置計算や監視は、scrollイベントを使用し、scrollTopやrectTopなどで行っていました。
しかし、以下のような問題点があります。

  • Forced Synchronous Layoutが発生する(レイアウトの再計算、詳しくはこちら What forces layout / reflow
  • Scroll Jankの可能性
  • そもそもの実装が面倒

Intersection Observerを使用することで、位置の監視をブラウザが行ってくれるため、パフォーマンスの改善につながります。

#サンプル
従来の手法とIntersection Observer APIを使った手法で、画像遅延読み込みのサンプルを実装しました。

##従来の手法

none_observer.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title></title>
  <style>
    .image {
      width: 400px;
      padding: 10px;
      background: #efefef;
      margin: 0 auto 20px auto;
    }
    .image img {
      display: block;
    }
  </style>
</head>
<body>
  <div class="image" data-image="image01.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image02.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image03.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image04.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image05.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image06.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image07.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image08.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image09.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image10.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image11.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <div class="image" data-image="image12.jpg">
    <img src="" alt="" width="400" height="284">
  </div>
  <script>
    (function() {

      const ROOT_MARGIN = 200;

      const images = document.querySelectorAll(".image");

      // イベントの間引き
      const throttle = (func, wait)=> {
        let time = Date.now();
        return ()=> {
          if ((time + wait - Date.now()) < 0) {
            func();
            time = Date.now();
          }
        }
      }

      class LazyLoad {
        constructor(image) {
          this.isLoaded = false;

          this.windowHeight = window.innerHeight;

          this.image = image;
          this.rect = this.image.getBoundingClientRect();

          this.onScroll = this.onScroll.bind(this);
        }
        addImage() {
          const imagePath = "img/" + this.image.getAttribute("data-image");
          this.image.children[0].setAttribute("src", imagePath); 
        }
        checkImageLoad() {
          let point = this.rect.top - ROOT_MARGIN;
          if(this.windowHeight > point) {
            this.addImage();
            this.isLoaded = true;
          }
        }
        onScroll() {
          if(this.isLoaded) return false;
          this.rect = this.image.getBoundingClientRect(); //スクロールの度に画面全体の再レイアウトが発生
          this.checkImageLoad();
        }
        addEventListener() {
          window.addEventListener("scroll", throttle(this.onScroll, 100), {passive: true});
        }
        init() {
          this.checkImageLoad();
          this.addEventListener();
        }
      }

      Array.from(images).map(image => {
        const lazyLoad = new LazyLoad(image);
        lazyLoad.init();
      });

    }());

  </script>
</body>
</html>

scrollイベントで監視を行い、対象のDOMがviewportの中に入ったらcallbackを実行しています。scrollの度に位置を取得しているため、画面全体のレイアウト(リフロー)が発生しパフォーマンスに影響を与えます。

##Intersection Observerでの手法

html/cssは上記のソースコードと同じため、Javascript部分のみ記載します。

observer.js
(function() {

  const ROOT_MARGIN = '200px 0px';

  const images = document.querySelectorAll(".image");

  const intersectionChanged = (entries) => {
    for (let entry of entries) {
      let scopeElm = entry.target;
      let imagePath = "img/" + scopeElm.getAttribute("data-image");
      if(entry.isIntersecting) {
        scopeElm.children[0].setAttribute("src", imagePath); 
      }
    }
  }

  class LazyLoad {
    constructor(image) {
      this.image = image;
    }
    init() {
      let observer = new IntersectionObserver(intersectionChanged, {
        root: null,
        rootMargin : ROOT_MARGIN
      });
      observer.observe(this.image);
      }
    }
      
  Array.from(images).map(image => {
    let lazyLoad = new LazyLoad(image);
    lazyLoad.init();
  });

}());

従来の手法でいうscroll時の監視を、全てIntersectionObserverクラスが行ってくれます。

###解説
Intersection Observerにはいくつかのコールバック引数と、3つのオプションがあります。

####コールバック引数

observer_callback.js
const intersectionChanged = (entries) => {
  for (let entry of entries) {

    // 変更が起こったタイムスタンプ
    console.log(entry.time);               

    // ルートのgetBoundingClientRect()
    console.log(entry.rootBounds);         

    // ターゲットのgetBoundingClientRect()
    console.log(entry.boundingClientRect); 

    // 交差領域のgetBoundingClientRect()
    console.log(entry.intersectionRect);

    // 交差している領域の割合
    console.log(entry.intersectionRatio);

    //交差しているかどうか
    console.log(entry.isIntersecting);

    // ターゲット
    console.log(entry.target);

  }
}

####オプション

observer_option.js
let observer = new IntersectionObserver(intersectionChanged, {

  // 交差対象のDOMの指定。初期値はdocument(viewport)。
  root: document.getElementbyid('target'),

  // entry.intersectionRatioを呼び出すタイミングを指定できる(下記は10%ごとに呼び出す)
  threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],

  // viewportと交差する何px前に呼び出したい場合や交差後何px後に呼び出したい場合に指定(下記は交差前200pxの部分でコールバック)
  rootMargin: "200px"

});
observer.observe(target);


対応ブラウザは相当限られるので使用の際はご注意ください。
(17/5/22現在、Chromeのみ、Node.jsのローカルサーバーで確認)

以下の記事を参考にさせていただきました。
Intersection Observer を用いた要素出現検出の最適化

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?