SAMPLE

スクロールアニメーション(Intersection Observer)

Written by okoshi

INDEX

説明

IntersectionObserver API(交差監視 API)を使い、ビューポートに要素が入ってきたかどうかを判別します。

使用するライブラリ

なし

手順

手順1

下記のような aria-hidden="true"となっている section が、ビューポート(ブラウザ画面内)に入ってきたらaria-hidden="false"に切り替わって表示されるようにしていきます。

<section class="section" aria-hidden="true">
  <h2>Heading</h2>
  <p>Lorem ipsum...</p>
</section>
<section class="section" aria-hidden="true">
  <h2>Heading</h2>
  <p>Lorem ipsum...</p>
</section>
...

手順2

CSSで.sectiontransitionaria-hiddenでの各状態におけるスタイルを指定します。

.section {
  & {
    padding: 1rem;
    transition: opacity 1s ease, visibility 1s ease;
  }
  &[aria-hidden="true"] {
    opacity: 0;
    visibility: hidden;
  }
  &[aria-hidden="false"] {
    opacity: 1;
    visibility: visible;
  }
}

手順3

IntersectionObserver.sectionに対して設定していきます。

基本的にはconst observer = new IntersectionObserver(callback, options)で、交差時に実行されるcallbackoptionsを渡してオブザーバーを作成したら、observe()で要素を指定します。

optionsの中身は下記の通り。

  • root:基準となる要素を指定します。デフォルトはnullで、最上位のビューポート=ブラウザの領域となります。
  • rootMargin :デフォルトは'0px'rootからのオフセットを指定します。cssのmargin の様に'0px 0px 0px 0px''100px 0px' のような形で記載します。0の場合もpxや%の単位を指定しないとエラーになるので注意してください。rootnullのとき'-50% 0' を指定すると、画面の真ん中が基準になります。
  • thresholdcallback が実行されるタイミングを指定します。0〜1の範囲で設定し、0は要素が入ってきたとき、1が要素が100%表示されたときです。[0, 0.5, 1] だったら要素が入ったとき、半分表示された時、全部表示されたときにcallbackが実行されます。デフォルトは0.0

callbackIntersectionObserverEntryオブジェクトのリストとオブザーバーを受けとります。

IntersectionObserverEntryについては下記の通り。

  • IntersectionObserverEntry.target :交差する要素を返します。
  • IntersectionObserverEntry.isIntersecting:要素が少しでもビューポートに入っている場合 true、全く入っていない場合 falseを返します。
  • IntersectionObserverEntry.intersectionRatio:要素がどの程度表示されているかを0〜1の範囲の数値で取得することができます。(thresholdと同じルールです)
  • IntersectionObserverEntry.boundingClientRect:ターゲット要素の矩形の境界を返します。(具体的にはtop,right,bottom,left,x,y,width,heightが取得できます)
  • IntersectionObserverEntry.intersectionRect :ターゲットの表示領域を返します。boundingClientRectと同様にtop,right,bottom,left,x,y,width,heightが取得できますが、簡単に説明すると縦スクロールの場合は見えている領域に応じて heightの数値が変わっていきます。
  • IntersectionObserverEntry.rootBounds はルートとなる要素の矩形を返します。
  • IntersectionObserverEntry.timeはIntersectionObserverが作成された時間を基準として、交差が記録された時刻を返します(単位はミリ秒)

サンプル1では要素が入ったかどうかの確認のみなので optionsは全てデフォルト、callbackでもIntersectionObserverEntry.isIntersectingIntersectionObserverEntry.targetのみを使用します。

※ 複数のsection要素に同じ指定をするためobserver は配列に格納しています

// IntersectionObserver の threshold で実行されるアクション
// threshold は デフォルトで0.0 = 要素が画面内に入った時
const callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) { // 要素がビューポートに入ったとき
      entry.target.setAttribute('aria-hidden', 'false')
    } else { // 要素がビューポートから外れたとき
      entry.target.setAttribute('aria-hidden', 'true')  
    }
  })
}

// observer が複数なので、格納するための配列を作成
const observers = [];

// 対象としたい要素を指定
const sections = document.querySelectorAll('.section')

// IntersectionObserver を sectionsに適用
sections.forEach((e, i) => {
  observers[i] = new IntersectionObserver(callback) // IntersectionObserber に callbackを渡してobserversのi番目の配列に格納
  observers[i].observe(e) // 要素(e) を IntersectionObserver によって監視される対象要素に追加
})

サンプル1

上記の結果がこちら。<section>aria-hiddenを切り替える代わりに <img> タグのsrcを置き換えるような形にすると、loading="lazy"が実装されていないブラウザ向けのlazyloadを作成することもできます。

サンプル2

こちらはthreshold[0.00, 0.01, 0.02,... 0.99, 1.00] の100段階にし、IntersectionObserverEntry.intersectionRatio の値でアニメーションさせたサンプルになります。

※わかりやすい様に intersectionRatioの値を%に変換して表示しています。

A:透明度を変更(opacityを0→1)

B:ぼかしを変更(blurを10px→0)※コンテンツ高さ>ビューポート高さだとぼかしがかかったままになります

C:背景色を変更(hslのh:色相の値を 0→360)

D:見出しを回転(h2 > spanのrotateの値を0deg→720deg)

E:見出しを移動(h2 > spanのtranslateXを0→200%)

参考文献