[ js ]「スクロールして特定の位置をすぎたらふわっと表示」を簡単に実装する方法[IntersectionObserver]

「スクロールして特定の位置をすぎたらふわっと表示」を簡単に実装する方法

素のJS(Vanilla JS) で、特定の位置をすぎたら表示、という状態をつくってみます

この記事は、素のjavaScriptで(jQuery等を使わずに)
特定の位置をすぎたら」を条件に、何らかの処理を実行する
という事をするための方法を書いた記事です。

次のくだりは、Webデザイナーとしての日常の話の一例です。

自身が、Webサイトを制作する案件を
デザイン〜コーディングまで担当していることを想像してください。
その担当している案件で、クライアントさんやディレクターなど、進行・決裁権を持っている人物から

ここのセクションは、要素をふわっと表示させたいんですよね
スクロールして通りすぎたら、この要素をスライドインさせてほしい

…こういった要望をいただくこと、普通にあると思います。

こういった場合、以前の手法としては
・ブラウザのTOPの位置から現在の位置までがどれだけスクロールされているかの値をとる。
・上記のスクロールの値と、対象となる要素の上端の座標を比較して、そこを通過したらアクションを起こす。

↑という様な方法で実装するパターンが多かったかと思います。
もしくは、何らかのライブラリを入れる等していた方も多いかもしれません。

今回は、それに代わる方法として
IntersectionObserver というものを使用した方法について、記事を書いていきます。
(この IntersectionObserver は、lazyloadと組み合わせるケースなどにおいてGoogleも使用を推奨しており、今後スタンダードになっていく方法と思われます。)

高機能で使うのも簡単!Viewportに入ったかどうかを判定できる、IntersectionObserver API

IntersectionObserver API とは、
交差(Inetrsection)観察(Observe)する、という名前が示す通り、

ある要素が
Viewportの上端(もしくは指定した点)と交差したかどうか

を判定してくれる機能を持ったAPIになります。
APIと言っても、何か特別なライブラリの読み込みが必要なわけではなく
それに対応しているブラウザであれば、javaScriptから呼び出すだけで使用が可能です。

使用する手順

使用する大まかなイメージは以下の通りです。

(1).表示させたい要素を決めて、id もしくは class を付与しておきます。
(2).それをjavaScript側で document.querySelector 等を使用して、対象として指定。
(3).IntersectionObserver に渡す option を、javaScript で設定します。
(4).ある要素が、Viewport に入った時に実行される関数を、javaScript で定義。
(5).new IntersectionObserver() として、IntersectionObserver を作成。(3)、(4)と紐づけます。

あとは、たとえば「ふわっと表示させる」ことが目的であれば、
それを実現するcssアニメーションを定義しておき、条件を満たしたら(上記の(1)、(2)で対象にした要素がViewportに入ったら)
そのclassを付与するようにしてあげれば良い、というわけです。
( ↑ あえていうならこれを手順(6).としましょうか)

それでは次のセクションで、それぞれのコードを書いていきます。

html, js, css, それぞれのコードをご紹介

上記の手順の、まずは(1)についてです。htmlで、ターゲットとする対象の要素に
idまたはclassを付与します。今回の例ではclassを付与しています。

[html]

<div class="target-container is-target">
  <div class="target-item">
    ここが、ふわっと表示させたい要素
  </div>
</div>

↑極力シンプルな例にするため、この記事では、対象の要素が1つの場合の例を書きます。
このように単純に親要素、子要素がひとつずつの要素があったとします。

1行目の is-target という class を持っている要素が対象になる想定です。
識別できれば名前は何でもよく、(重複しないなら)id指定でも大丈夫です。

また、ふわっと表示させたいので、
is-target には css側であらかじめ
oapcity: 0; を指定しておきます。
(↑これを、条件を満たした時にcssアニメーションで opacity: 1; にして、ふわっと表示させるというわけです)

javascript側のコード

次に、javascript側のコードの全体像です。(2) ~ (5)まで、次のような
比較的短いコードで実現できます。

[javascript]

const myTarget = document.querySelector('.is-target'); // ...手順(2)の部分

// makeObserver(); の実行(関数名は何でも良いです)
makeObserver();

// IntersectionObserverを作成する関数
function makeObserver() {
  let myObserver;

 // IntersectionObserverのオプション設定 ...手順(3)の部分
  let myOptions = {
    root: null,
    rootMargin: '0px 0px',
    threshold: '0'
  };
  
  // IntersectionObserverの作成 ...手順(5)の部分
  myObserver = new IntersectionObserver(myIntersect, myOptions);

  // observe(対象); として対象を監視
  myObserver.observe(myTarget);
}

// 条件を満たした実行される関数 ...手順(4)の部分
function myIntersect(entries, myObserver) {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      myTarget.classList.remove('is-target');
      myTarget.classList.add('is-animation');
    } else {
      myTarget.classList.remove('is-animation');
      myTarget.classList.add('is-target');
    }
  });
}

1行目が、手順(2)の、
document.querySelector を使用して、ターゲットとする要素を対象として指定している部分です。

11行目の let myOptions = { … の部分が、手順(3)
IntersectionObserver に渡す option を、設定している部分です。
具体的には、次の3つのオプションを指定できます。

root … ターゲットにした要素が、見えているかどうかを判定する基準となる要素を定義する項目です。この値が null の場合はデフォルト値となり、ブラウザの Viewport そのものが基準という事になります。
rootMargin … 上記の root のViewportの端から、どれくらい交差したら条件を満たすかの基準を定義できます。値は px値、または %値 で指定できます。
threshold … 条件が満たされてから、さらに対象の要素がどれくらい表示されたらIntersectionObserverに設定したcallback関数を実行するか、という事を決めている設定です。threshold(閾値=いきち、しきい値)の名前が示す通り、ここで設定する値は「どれだけ通りすぎたのか」という事を示す割合です。
つまり、1割通りすぎた場合に実行すれば良いなら、0.1と設定します。

25行目~末尾まで、 function myIntersect(entries, myObserver) { … で始まる部分が手順(4)の部分です。
ここに定義してある関数が、条件を満たした時に実行されます。
今回は「ふわっと表示する」ので、
is-animation という cssアニメーションを実行する class をあらかじめ定義しておいて、
条件を満たしたら、手順(2)で指定したターゲット要素にclass付与する、
ということが書かれています。
27行目に entry.isIntersecting とありますが、
isIntersecting というのは
IntersectionObserver に渡すことのできる値のひとつで、
対象の要素が」「Viewportと交差した時」を意味しています。

if がViewportと交差した時に実行される部分、
else がViewportと交差しなくなった時に実行される部分
になります。

18行目が手順の(5)
IntersectionObserver を作成している部分です。
ここで、
myObserver = new IntersectionObserver(myIntersect, myOptions);
として、手順 (3)、(4) で定義した関数、オプションを紐づけています。

IntersectionObserver を作成したら、
21行目にあるように
myObserver.observe(myTarget);
として、対象の要素(ここでは手順(2)で指定したmyTarget)
を監視します。

cssアニメーション用のコード

次は、条件を満たしたら付与されるcssアニメーションについての記述です。

.is-animation {
  animation: myanimation 1s ease-in;
}

@keyframes myanimation {
  0% {
    transform: scale(1.5);
    opacity: 0;
  }
  75% {
    opacity: 0.75;
    transform: scale(1);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

↑cssは、この様に定義しておきます。
条件を満たしたら、
is-animation という class が付与されて、
opacity が 0 → 1 になるアニメーション付きで
ふわっと要素が表示されるというわけです。

以上の記述で、次のサンプルのような表示が実現可能になります。
※上記は、IntersectionObserverをテストして見る場合の、最小限の構成です。
もちろん、ふわっと表示させる要素などは、適宜スタイルを当てるなどしてリッチにして問題ないです。

IntersectionObserverのサンプル

実際に IntersectionObserver が動いているサンプルを作成してあります。
次のリンクより、確認することができます。
IntersectionObserverのサンプル

↑なお、こちらのリンク先のサンプルでは
複数のカード型コンテンツ
順番にフェードインします。

今回の記事でご紹介した上記のコードは、1つのコンテンツのみのものになりますが、
工夫次第で、リンク先のサンプルのように
複数のコンテンツをフェードイン
させることも可能です。

ひとつの IntersectionObserver で、複数のコンテンツを監視する方法は、
この記事の続編
[ js ]複数の要素を監視して、Viewportに入ったら表示させる方法[IntersectionObserver]
にてご紹介しています。あわせてぜひご覧ください!

ポリフィルについて

このIntersectionObserverですが、やはり
IE11だと動作しません。

それ以外のブラウザであれば、ほぼすべて動作対象 な状況ではあるのですが…。
↑リンク先はMDN Web Doc内、Intersection_Observer_APIのページの対象ブラウザが見れます

そこでどうするかというと、IntersectionObserverにも、幅広いブラウザで動作させるためのポリフィルが存在します。

次のページに方法が記載されています。
IntersectionObserver | GitHub
↑このページからポリフィル用のスクリプトをダウンロードして対象のページに読み込ませるか、
ページ末尾の Browser support セクションに書いてある、Webサービスを利用する方法の、2通りの選択肢があります。

いずれにしても、2022年6月15日で、IE対応という行いは不要になりますので、それまでの。あと半年と少しの我慢です。

この記事のまとめ

今回は、IntersectionObserverについて、ご紹介しました。

従来の、
・ブラウザのTOPの位置から現在の位置までがどれだけスクロールされているかの値を取得
・対象となる要素の上端の座標を比較して、そこを通過したらアクションを起こす
というような方法で実装する場合だと、

window.addEventListener('scroll', () => { ...

↑このようにして、ずっとページ全体の scrollイベント を監視し続けることになり、
パフォーマンス的にもあまりよろしくありませんでした。

今回ご紹介した、IntersectionObserver を使うと、
比較的すっきりした記述で
Viewportに要素が入った時に何かを実行する
ということが実現できるので、個人的には気に入っている方法になります。

また、冒頭に書いた通り、lazyload と組み合わせるケースなどにおいて
Googleが公開している記事内でも使用を推奨している手法ですので、
その点においても知っておくと良い方法かと思います。

※この記事は、内容の品質向上を目的に、随時更新を行う場合があります。