要素を監視して画面に入ったらふわっと表示させるスクロールアニメーション

JavaScriptのIntersectionObserverを利用し、画面内に要素が入ったらふわっと表示させるスクロール連動アニメーションを作成したいと思います。

目次

IntersectionObserverで要素を監視する

IntersectionObserverを使って、アニメーションさせる対象の要素を監視します。

今回はjs-in-viewというクラスの要素が画面内に入ってきたら、属性(もしくはクラス)を与えるといった処理を実装していきます。

基本形

基本的にはこれでOK。

// IntersectionObserver を作成
const observer = new IntersectionObserver((entries) => {
  entries.forEach(function (entry) {
    // 要素がビューポートに入った場合
    if (entry.isIntersecting) {
      // 表示属性を付与
      entry.target.setAttribute("data-visible", "true");

      // 一度監視したら、対象要素の監視を停止(必要に応じてコメントアウト)
      observer.unobserve(entry.target);
    } else {
      // ビューポートから外れたら非表示属性を付与
      entry.target.setAttribute("data-visible", "false");
    }
  });
});

// 監視要素
const viewElements = document.querySelectorAll(".js-in-view");
viewElements.forEach(function (el) {
  // 各要素をオブザーバーで監視
  observer.observe(el);
});

observer.unobserve(entry.target)をコメントアウトすると、data-visibleの値を切り替えられるようになり、繰り返しアニメーションさせることが可能です。

発火位置を変更してみる

要素が画面のどの位置にきたら発火するかoptionsに指定し、IntersectionObserverに渡すことで、カスタマイズされたタイミングで動作させることができます。

const options = {
  root: null, // ビューポートをルートとして使用
  rootMargin: "-10% 0%", // 交差の有効範囲をpx % で指定
  threshold: 0, // 要素が何割見えたら発火するかを指定(最小0.0〜最大1.0)
};

const observer = new IntersectionObserver((entries) => {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {
      entry.target.setAttribute("data-visible", "true");

      // 一度監視したら、対象要素の監視を停止(必要に応じてコメントアウト)
      observer.unobserve(entry.target);
    } else {
      entry.target.setAttribute("data-visible", "false");
    }
  });
}, options);

const viewElements = document.querySelectorAll(".js-in-view");
viewElements.forEach(function (el) {

  observer.observe(el);
});
root監視対象の交差を検知する枠。
nullだとビューポートが基準となる。
rootMarginrootからのオフセットをpxか%で指定。
デフォルトは0px 0px 0px 0px(上右下左)。
マイナスで内側にオフセットされる。
threshold要素がどの程度見えたら検知するかの割合。
50%で検知させる場合は0.5を指定。
(最小0.0〜最大1.0)

rootMarginを"-10% 0%"にすると、画面内の上下10%までは検知の対象外となります。

個別にアニメーションの動きを上書きしてみる

IntersectionObserverとは直接関係ありませんが、HTML上で各要素ごとに遅延やアニメーションの長さを設定できるように少し工夫します。

アニメーションさせる要素にカスタムデータ属性(data-delaydata-duration)を追加して、任意の秒数を指定します。

<div class="js-in-view fadeInUp" data-delay=".25s" data-duration="1s">

各カスタムデータ属性の値をそのままanimation-delayanimation-durationへ反映させて、スタイルとして要素に追加すれば完了です。

const options = {
  root: null, // ビューポートをルートとして使用
  rootMargin: "-10% 0%", // 交差の有効範囲をpx % で指定
  threshold: 0, // 0〜1の割合で要素が可視になった時にコールバックを発火
};

const observer = new IntersectionObserver((entries) => {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {
      entry.target.setAttribute("data-visible", "true");

      // 一度監視したら、対象要素の監視を停止(必要に応じてコメントアウト)
      observer.unobserve(entry.target);
    } else {
      entry.target.setAttribute("data-visible", "false");
    }
  });
}, options);

const viewElements = document.querySelectorAll(".js-in-view");
viewElements.forEach(function (el) {

  // delayの上書き
  const delay = el.getAttribute("data-delay");
  if (delay) {
    el.style.animationDelay = delay;
  }

  // durationの上書き
  const duration = el.getAttribute("data-duration");
  if (duration) {
    el.style.animationDuration = duration;
  }

  observer.observe(el);
});

CSSの記述例

フェードインアップの例です。

/* スクロールアニメーション
----------------------------------------------- */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
}

// デフォルトのアニメーション設定
[data-visible] {
  animation-fill-mode: both;
  animation-duration: 2s;
}

// フェードイン系はあらかじめ非表示にしておく
.fadeIn,
.fadeInUp {
  opacity: 0;

  &[data-visible="true"] {
    opacity: 1;
  }
}

// アニメーション指定
[data-visible="true"] {
  &.fadeInUp {
    animation-name: fadeInUp;
  }
}

// JSが効かない時のフォールバック(フェードイン系を強制表示)
@keyframes visible {
  to {
    opacity: 1;
    visibility: visible;
  }
}
:not([data-visible]).fadeInUp {
  animation-name: visible;
  animation-fill-mode: forwards; // 最後のキーフレームを保持
  animation-duration: 1s;
  animation-delay: 1s;
}

シンプルなアニメーションであればtransitionでも十分ですが、バウンドのような複雑なアニメーションを加える可能性があるなら、animationプロパティに統一しておいた方が無難です。

どちらでも良いですが、2種類のアプローチが混ざるとメンテナンス性が悪くなるので、どちらか一方に統一することをおすすめします。

そして、このCSSで重要なのは、JavaScriptが効かない時のフォールバック処理です。

フェードインアニメーションでは、最初に要素が一瞬見えないようにopacity:0を設定することがよくありますが、JavaScriptが無効になっているといつまで経っても表示はされません。

それを防ぐために、data-visible属性の有無でJavaScriptが有効かどうかを判断し、無効時には要素を強制的に表示するようにしています。

HTMLの記述例

<div class="js-in-view fadeInUp" data-delay=".25s" data-duration="1s">
  • js-in-view:監視対象(JSで管理)
  • fadeInUp:任意のアニメーション(CSSで指定)
  • data-delay:遅延させる秒数(オプション)
  • data-duration:アニメーションの長さ(オプション)

data-visibleはJavaScriptによって付与するため、直接記述しません。

課題:個別のオフセットを指定したい

まだ良いアプローチが見つかっていないのが、各要素個別にオフセット(どこで発火させるか)を設定する方法です。

理想としては、カスタムデータ属性data-offsetを与え、値が50%であればビューポートの50%の位置で発火というようにしたいんですよね。

各要素ごとにIntersectionObserverを作成すれば実現は可能なのですが、JavaScriptが複雑になることと、パフォーマンスが非常に悪くなるため、一つのIntersectionObserverで管理できれば良いなと。

optionsのrootMarginは、後から動的に変更できないので、IntersectionObserverの中で判定できる方法を模索中です。

おすすめWEBスクール

WEB制作やWEBデザインを学びたいなら、SNSでも話題の「デイトラ」がおすすめ!
どのコースも10万円前後と業界最安値で、副業や転職に向けて十分なスキルを身につけることができます。

役に立ったら他の方にシェア

お気軽にコメントどうぞ

コメントする

目次