【JavaScript】IntersectionObserverでスクロール時に要素をふわっと表示させる方法

【JavaScript】IntersectionObserverでスクロール時に要素をふわっと表示させる方法 JavaScript

スクロールして画面内に入った要素が「ふわっ」と表示される演出は、多くのサイトで使われています。かつては scroll イベントで実装していましたが、現在はIntersectionObserver を使うことで、軽量かつ高パフォーマンスに実現できます。

この記事の結論:要素を初期状態で opacity: 0 にしておき、IntersectionObserver で画面に入ったら .active クラスを付けてCSSの transition でフェードアップさせます。一度きりなら unobserveprefers-reduced-motion への配慮も加えると親切です。
スポンサーリンク

完成イメージ(動くデモ)

↓ このあたりをゆっくりスクロールすると、ボックスがふわっと現れます:

ふわっと①
ふわっと②
ふわっと③

IntersectionObserverとは?

IntersectionObserver は、指定した要素がビューポートに入ったかを効率的に監視するAPIです。scroll イベントを多用せずに済むため、パフォーマンスに優れています。基本的な使い方はIntersectionObserverで複数要素を監視する方法で解説しています。

HTMLの準備

フェードインさせたい要素に共通のクラス(ここでは fade-in)を付けます。

HTML
<div class="fade-in">コンテンツ1</div>
<div class="fade-in">コンテンツ2</div>
<div class="fade-in">コンテンツ3</div>

CSSでフェードアップを定義

初期状態は opacity: 0 +少し下にずらし(translateY)、.active が付いたら元の位置・不透明に transition させます。

CSS
.fade-in {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.fade-in.active {
  opacity: 1;
  transform: translateY(0);
}

JavaScriptでIntersectionObserverを設定

対象要素を監視し、画面に入った(isIntersecting)ら .active を付与します。一度表示したら unobserve で監視を解除します。

JavaScript
document.addEventListener("DOMContentLoaded", () => {
  const targets = document.querySelectorAll(".fade-in");

  const observer = new IntersectionObserver((entries, obs) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add("active");
        obs.unobserve(entry.target); // 一度きり
      }
    });
  }, {
    threshold: 0.1, // 10%見えたら発火
  });

  targets.forEach((t) => observer.observe(t));
});

一度だけ&しきい値の調整

  • unobserve:一度表示したら監視を解除(無いと出入りのたびに再発火)
  • threshold:どれくらい見えたら発火するか(0.1=10%)
  • rootMargin: "0px 0px -100px 0px":「画面に入る少し手前」で発火させたいときに調整

順番に少しずつ表示する(stagger)

複数の要素を少しずつ時間をずらして表示すると、グッと洗練された印象になります(AOSなどのライブラリでお馴染みの演出)。各要素にインデックスに応じた transition-delay を設定するだけです。上のデモも、この方法でずらして表示しています。

JavaScript:インデックスで遅延をつける
const targets = document.querySelectorAll(".fade-in");

targets.forEach((el, i) => {
  el.style.transitionDelay = `${i * 0.12}s`; // 0.12秒ずつ遅らせる
  observer.observe(el);
});

CSSの :nth-child でも遅延をつけられますが、要素数が可変なら上記のようにJavaScriptで付ける方が柔軟です。

応用:毎回アニメ・別の動きにする

用途に合わせて、ふるまいや動きを変えられます。

  • 画面に入るたび毎回アニメさせたいなら、unobserve せず、isIntersecting.active付け外しします(出入りのたびに再生)
  • 横からスライド:CSSの transformtranslateX(40px) に変える(詳しくは画面の横からフェードインさせる方法
  • ズームしながら表示transform: scale(0.9)scale(1) に変える
JavaScript:毎回アニメさせる(unobserveしない)
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    // 入ったら付ける/出たら外す(毎回再生される)
    entry.target.classList.toggle("active", entry.isIntersecting);
  });
}, { threshold: 0.1 });

prefers-reduced-motionに配慮する

動きが苦手なユーザーのために、「視差効果を減らす」設定ではアニメーションせず最初から表示するようにします。

CSS:動きを減らす設定では即表示
@media (prefers-reduced-motion: reduce) {
  .fade-in {
    transition: none;
    opacity: 1;
    transform: none;
  }
}

関連するアニメーション

よくある質問(FAQ)

Qスクロールで要素が見えたらアニメーションを開始するには?
AIntersectionObserver で要素が画面に入ったときにCSSクラス(.active)を追加し、CSSで opacitytransformtransition を定義します。コールバック内で unobserve すると一度だけアニメーションします。
Q一度表示されたら再度アニメーションさせないようにするには?
Aコールバック内で observer.unobserve(entry.target) を呼び、その要素の監視を解除します。これをしないと、スクロールで出入りするたびに繰り返しアニメーションします。
Q発火するタイミングを調整するには?
Athreshold(見えた割合)と rootMargin(基準の余白)で調整します。rootMargin をマイナスにすると「画面に少し入ってから」発火させられます。
Qアニメーションが苦手なユーザーへの配慮は?
A@media (prefers-reduced-motion: reduce)transition を無効化し、最初から表示する(opacity: 1)ようにします。アクセシビリティの基本配慮です。
Q複数の要素を順番に表示するには?
A各要素に transition-delay をインデックスに応じて設定します。el.style.transitionDelay = `${i * 0.1}s` のように少しずつ遅らせると、順番にずれて表示される stagger 演出になります。

まとめ

スクロール時のふわっと表示は、初期状態を opacity: 0 にしておき、IntersectionObserver で画面に入ったら .active を付けてCSSの transition でアニメーションさせるのが定番です。

一度きりなら unobserve、発火位置は threshold / rootMargin で調整。prefers-reduced-motion への配慮も忘れずに、軽快で親切な演出にしましょう。