• 作成日:

【JavaScript】スクロールアニメーションをIntersection Observerを使って実装する方法

今回はIntersection Observerを使ったスクロールアニメーションを実装する方法を紹介します。まずはデモサイトを作成したので確認してみてください。

デモサイト

前提条件は以下

  • jQueryは使わない
  • IE11に対応する
  • 要素ごとに複数のアニメーションを指定

説明する環境は以下

  • macOS Catalina v10.15.5
  • Visual Studio Code v1.50.1

スクロールアニメーションをIntersection Observerを使って実装する方法

以下のようなscrollイベントは、スクロールする度にブラウザに処理されるため負荷が高くなり、サイトが重くなる原因につながります。

window.addEventListener('scroll', function(){
   //ここに処理を書く
});

Intersection Observerは対象の要素が、画面の指定した位置に来ただけイベントが発生するのでブラウザへの負担を低くできます。

それでは以下のデモサイトを元に使い方を説明していきます。

デモサイト

HTMLの書き方

以下はデモサイトのアニメーションさせている部分になります。

この部分のHTML構造は以下のように書きました。

<section class="demo__section js-trigger">
  <div class="demo__section_inner">
    <div class="demo__block is-fade_in_zoom">
      <div class="demo__img">
        <img src="./images/observer-img03.jpg" alt="" />
      </div>
      <div class="demo__img">
        <img src="./images/observer-img04.jpg" alt="" />
      </div>
    </div>
  </div>
</section>

ここで重要はクラス名は以下の2つです。

  • js-trigger … このクラス名がある要素が画面の中に入ったらアニメーションが発火します。
  • is-fade_in_zoom … このクラス名に合うアニメーションを実行します。

JavaScriptの書き方

ie11に対応させるため、古い書き方で書いています。

var option = {
  root: null, //nullでブラウザ画面を対象にする
  rootMargin: "0% 0% -20% 0%", //画面の下から-20%の位置をターゲットと交差する位置に指定
  threshold: 0.2, //指定した要素が画面に20%入るとコールバッイベント発生
};

// 交差した際の処理を記載
var callback = function (entries) {
  
  entries.forEach(function (entry) {
    
    if (entry.intersectionRatio > 0.2) { //交差する位置が20%を超えたら…

      var targets = entry.target.getElementsByClassName("demo__block"); //クラス名demo__blockのHTML要素を取得
      var targetsArray = Array.prototype.slice.call(targets, 0); //配列に変換

      targetsArray.forEach(function (item) { //配列化したものを1つ1つ処理していく
        var targetClassList = item.classList; //demo__blockと一緒に指定されているクラス名を取得

        if( targetClassList.contains('is-fade_in_side') == true ){ //クラス名is-fade_in_sideがあれば…
          targetClassList.add("is-fade_in_side--done"); //クラス名is-fade_in_side--doneを付与する
        } else if (targetClassList.contains('is-fade_in_updown') == true) { //クラス名is-fade_in_updownがあれば…
          targetClassList.add("is-fade_in_updown--done"); //クラス名is-fade_in_updown--doneを付与する
        } else if(targetClassList.contains('is-fade_in_zoom') == true){ //クラス名is-fade_in_zoomがあれば…
          targetClassList.add("is-fade_in_zoom--done"); //クラス名is-fade_in_zoom--doneを付与する
        } 
      });
      observeres.unobserve(entry.target); //ターゲットの監視を終了
    }
  });
};
var observeres = new IntersectionObserver(callback, option); //引数callback、optionを持ったobserverインスタンスを作成

var trigger = document.querySelectorAll(".js-trigger"); //ターゲットとなるクラス名js-triggerをすべて格納
var triggerArray = Array.prototype.slice.call(trigger, 0); //ターゲットを配列に変換

triggerArray.forEach(function (el) { //ターゲットごとにループする
  observeres.observe(el); //ターゲットごとに監視を開始する
});

コードの解説をしていきます。

アニメーションを開始させる要素を取得する

今回はjs-triggerというクラス名が特定の位置に来たときにアニメーションさせるようにしています。デモページにはjs-triggerが複数あるのですべて取得して、35行目で配列化しています。

var trigger = document.querySelectorAll(".js-trigger"); //ターゲットとなるクラス名js-triggerをすべて格納
var triggerArray = Array.prototype.slice.call(trigger, 0); //ターゲットを配列に変換

配列化したターゲットを1つずつ監視対象にする

配列化したターゲットをループさせて、1つ1つをIntersection Observer APIを使って監視を開始させます。

triggerArray.forEach(function (el) { //ターゲットごとにループする
  observeres.observe(el); //ターゲットごとに監視を開始する
});

observeresの部分は、32行目にある引数callback、optionを持ったobserveresインスタンスを指しています。

var observeres = new IntersectionObserver(callback, option); //引数callback、optionを持ったobserveresインスタンスを作成

では引数callbackとopationにはどんな内容が書かれているのでしょうか。まずはoptionから説明します。

optionで、イベント開始の位置を指定する

optionには、ターゲットがどの部分と交差したときにアニメーションを発火させるか指定できます。

var option = {
  root: null, //nullでブラウザ画面を対象にする
  rootMargin: "0% 0% -20% 0%", //ブラウザ画面の下から-20%の位置をターゲットと交差する位置に指定
  threshold: 0.2, //指定した要素が画面に20%入るとコールバックイベント発生
};
  • root … ターゲットを監視するエリアを指定。nullでブラウザ画面を対象。
  • rootMargin … 監視するエリアの中で、どの位置をターゲットとの交差点にするか指定。
  • threshold … ターゲットが画面内にどのくらい入ったらコールバックイベントを発生させるか、位置を指定。

rootをnullに設定して、rootMarginを0% 0% -20% 0%と指定すると、画面下から-20%の位置をターゲットとの交差位置に設定できます。

rootMarginの4つの数値の意味は、0%(上)0%(右)-20%(下)0%(左)と位置を指定できます。数値は%でもpxでも大丈夫です。

threholdは、ターゲットが画面にどのくらい入ったらコールバックイベントを発生させるか指定できます。threshold:0.2だと、ターゲットが画面に入って20%の位置でコールバックされます。

ではコールバックイベントの内容を説明します。

callbackでアニメーションを開始させる

コールバックイベントが発生すると、ターゲットがrootMarginで指定した位置を20%を超えているか判定。超えているとそれ以降の処置が処理が開始されます。

// 交差した際の処理を記載
var callback = function (entries) {
  
  entries.forEach(function (entry) {
    
    if (entry.intersectionRatio > 0.2) { 

      //交差する位置が20%を超えたら、この中の処理を開始      

    }
  });
};

交差した位置の割合が20%を超えたときの処理は以下になります。js-triggerにあるクラス名demo__blockのHTML要素を取得して、配列化。それを1つずつループさせて、demo__blockと一緒に指定されているクラス名を取得します。

取得したクラス名ごとに判定して、それに合うクラス名を追加していきます。例えばdemo__blockの他にis-fade_in_sideというクラス名があれば、is-fade_in_side--doneというクラス名を追加します。

var targets = entry.target.getElementsByClassName("demo__block"); //クラス名demo__blockのHTML要素を取得
var targetsArray = Array.prototype.slice.call(targets, 0); //配列に変換

    targetsArray.forEach(function (item) { //配列化したものを1つ1つ処理していく
    var targetClassList = item.classList; //demo__blockと一緒に指定されているクラス名を取得

      if( targetClassList.contains('is-fade_in_side') == true ){ //クラス名is-fade_in_sideがあれば…
        targetClassList.add("is-fade_in_side--done"); //クラス名is-fade_in_side--doneを付与する
      } else if (targetClassList.contains('is-fade_in_updown') == true) { //クラス名is-fade_in_updownがあれば…
        targetClassList.add("is-fade_in_updown--done"); //クラス名is-fade_in_updown--doneを付与する
      } else if(targetClassList.contains('is-fade_in_zoom') == true){ //クラス名is-fade_in_zoomがあれば…
        targetClassList.add("is-fade_in_zoom--done"); //クラス名is-fade_in_zoom--doneを付与する
      } 
    });

ループが終了したら、以下の指定で監視を終了させています。

observeres.unobserve(entry.target); //ターゲットの監視を終了

Sass側のアニメーションの指定

特定のクラス名を追加されたときにアニメーションさせます。以下はクラス名is-fade_in_zoom--doneが追加されたときのアニメーションの指定です。

/* アニメーションを使うため先にベンダープレフィックスを定義
–––––––––––––––––––––––––––––––––––––––––––––––––– */
@mixin keyframes($animation-name: animation) {
  @-webkit-keyframes #{$animation-name} {
    @content;
  }
  @keyframes #{$animation-name} {
    @content;
  }
}

/* アニメーションを使うため上とセット
–––––––––––––––––––––––––––––––––––––––––––––––––– */
@mixin animation($animation-name) {
  -webkit-animation: $animation-name;
  animation: $animation-name;
  -webkit-animation-fill-mode: both;
  animation-fill-mode: both;
}

/* zoomのkeyframes
–––––––––––––––––––––––––––––––––––––––––––––––––– */
@keyframes fade-in-zoom {
  0% {
    filter: blur(3px);
    transform: scale(1.2);
    opacity: 0;
  }
  100% {
    filter: blur(0);
    transform: scale(1);
    opacity: 1;
  }
}

/* クラス名の指定
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.is-fade_in_zoom {
  opacity: 0;
}
.is-fade_in_zoom.is-fade_in_zoom--done {
  opacity: 1;
  .demo__img {
    @include animation(fade-in-zoom 1s ease 0.1s);
  }
}

keyframsで事前にアニメーションの定義を書いておいて、JavaScript側でクラス名が追加されたときにアニメーションが開始するようにすれば、複数のアニメーションに対応できます。

デモサイト

IE11への対策

IE11はIntersection Observerに対応していませんが、以下のpolyfillを1行読み込ませるだけで対応可能になります。

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

CDNではなく、直接ファイルをインポートして対策したい方はgithubからIntersection Observer用のpolyfillをダウンロードできます。

さいごに

今回はjQueryを使わずに、Intersection Observerを使ってスクロールアニメーションを実装する方法を紹介しました。

スクロールイベントが発生しない分、ブラウザへの負担を軽減できること、またIE11への対策もできるのでオススメです。