【脱・scrollイベント】Intersection Observerで実装する軽量なスクロール連動アニメーション

Webサイトを見ていると、画面のスクロールに合わせて画像やテキストが「フワッ」と浮き出るアニメーション、よく見かけますよね! リッチな印象を与えてくれる定番の演出なので、実務のサイト制作でも実装する機会が非常に多い機能です。

この「スクロールに合わせて対象の要素にクラス(is-active など)を付与する」という処理。 一昔前は window.addEventListener('scroll', ...) のように、scroll イベントを使って実装するのが一般的でした。

しかし、実はその実装方法……サイトの動作を重くしてしまう原因になるかもしれないんです。

scrollイベントの罠
scrollイベントは、画面をほんの少しスクロールするだけで内部的に何十回・何百回も連続して処理が実行されてしまいます。要素の数が増えれば増えるほどブラウザに負荷がかかり、パフォーマンス(表示速度や滑らかさ)に悪影響を及ぼす可能性があります。

そこで今回は、モダンで超軽量な Intersection Observer(インターセクション・オブザーバー)API を使って、ブラウザに負担をかけずにサクッとクラスを付与する実装方法をご紹介します!

従来の scroll イベントでの書き方と比較しながら、「要素が画面の60%の位置に来たら発火させる」といった実務でよく使う位置調整のコツも解説していくので、ぜひ参考にしてみてください!

実際のデモ動作

See the Pen Intersection Observer by yoshi (@mikanbako) on CodePen.

ビューポートを基準としたデモ動作は以下のリンクから確認してください!

実装コードの紹介

それでは、実際にこの動きを実装するためのコードを見ていきましょう。
CSSで「初期状態」と「アニメーション後の状態」を作り、JavaScriptでクラスを付け外しします。

【HTML】

フワッと表示させたい要素に、目印となるクラス(今回は js-scroll-target)を付与しておきます。

<div class="p-spacer">下にスクロールしてみてね ↓</div>
<div class="p-box js-scroll-target">フワッ!</div>
<div class="p-spacer">もっとスクロール ↓</div>
<div class="p-box js-scroll-target">フワフワッ!</div>
<div class="p-spacer">ここまで見てくれてありがとう!</div>

【CSS】

対象の要素はあらかじめ透明にして少し下にずらしておき、is-active クラスが付与されたら元の位置に戻るように設定します。

.js-scroll-target {
  opacity: 0;
  transform: translateY(40px);
  transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.js-scroll-target.is-active {
  opacity: 1;
  transform: translateY(0);
}

【JavaScript】

最後に、今回の主役である Intersection Observer を使ったJavaScriptコードです。

const targets = document.querySelectorAll('.js-scroll-target');

const options = {
  root: null,
  rootMargin: "0px 0px -40% 0px", // 画面の下から40%(上から60%)が交差ライン
  threshold: 0
};

const observer = new IntersectionObserver((entries, obs) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-active');
      obs.unobserve(entry.target); // 一度発火したら監視を解除する
    }
  });
}, options);

targets.forEach(target => {
  observer.observe(target);
});

コードの解説・こだわったポイント

今回の実装で最もこだわったのは、「いかにブラウザに負担をかけず(軽量に)、かつ思い通りの位置でアニメーションを発火させるか」という点です。

以下の3つのポイントに絞って、実装の意図を解説します。

scroll イベントから Intersection Observer への移行

導入部分でも触れましたが、従来の scroll イベントを使った実装にはパフォーマンス上の懸念がありました。

従来のscrollイベントの問題点
window.addEventListener('scroll', ...) は、ユーザーが1ピクセルスクロールするたびに連続して関数を呼び出します。要素の位置を計算する getBoundingClientRect() などを毎回実行するため、ブラウザの描画処理を圧迫し、アニメーションのカクつき(ジャンク)の原因になりやすいです。

これを解決するのが Intersection Observer API です。 このAPIは「要素が画面(ビューポート)に入ったか・出たか」という交差のタイミングだけを非同期でブラウザに通知してくれます。スクロール中の無駄な計算が一切なくなるため、非常に軽量で滑らかなUXを提供できるようになります。

rootMargin で発火タイミングを自在に操る

要素が画面の一番下から少しでも見えた瞬間に発火してしまうと、ユーザーの目線がいく前にアニメーションが終わってしまうことがあります。 そこで、rootMargin を使って「画面の少し上(今回は上から60%の位置)に来たら発火する」ように調整しました。

const options = {
  root: null, // ブラウザの画面全体を基準にする
  rootMargin: "0px 0px -40% 0px", // 上・右・下・左 のマージン
  threshold: 0
};

rootMargin の指定方法のコツ
CSSの margin と同じく「上・右・下・左」の順で指定します。 今回の -40% は「下から40%分、交差の判定ラインを内側に狭める」という意味です。つまり、「画面の下から40%(=上から60%)の位置に要素が到達した時」に初めて交差したと判定されます。

0を指定する場合は0px のように単位もつける必要があります!

これにより、ユーザーがコンテンツを一番見やすい位置でフワッと表示させることができます。

unobserve で監視を解除し、さらに軽量化

今回の「フワッと表示させるアニメーション」は、一度だけ実行されれば十分な演出です。

if (entry.isIntersecting) {
  entry.target.classList.add('is-active');
  obs.unobserve(entry.target); // ★一度発火したら監視を解除!
}

要素が画面に入って is-active クラスを付与した直後に obs.unobserve(entry.target) を呼び出し、その要素の監視をストップしています。 アニメーションが終わった要素の監視を外すことで、ページのスクロールをさらに軽くする(メモリを節約する)工夫を取り入れています。

補足:ブラウザの対応状況について

Intersection Observer API はモダンなAPIですが、現在では主要なブラウザ(Chrome、Safari、Firefox、Edgeなど)で標準サポートされており、実務でも問題なく安心して採用できる技術です。

より詳しい仕様や、各ブラウザの最新のサポート状況については、以下の公式ドキュメントや対応表をご確認ください。

・Can I Use: IntersectionObserver
・MDN: Intersection Observer API

まとめ

今回は、Web制作における定番のアニメーション実装である「スクロール時のクラス付与」について、従来の scroll イベントから脱却し、Intersection Observer を使って軽量化する方法をご紹介しました。

「見た目が同じように動く」のであれば、よりモダンでブラウザに負担をかけない(パフォーマンスが良い)アプローチを選択する。そういった「裏側の動作やUXへの配慮」こそが、質の高いWebサイト制作において非常に重要だと考えています。

これからも、ただ目に見える機能を作るだけでなく、ユーザーにもブラウザにも優しいコード設計を意識しながら、フロントエンドの技術をどんどん磨いていきます!

最後まで読んでいただき、ありがとうございました!

Prev
Next