BLOG

Vanilla JSでフェードインアニメーションを作ってみた!

Written by uchida

INDEX

説明

Vanilla JS(ライブラリやフレームワークを使用しない、シンプルなJavaScript)とHTML、CSSのみを使った、フェードインアニメーションを実装してみます。

近年のWEBサイトではよく見る、サイト訪問者の画面スクロールに合わせて要素が下からふわっと現れるアニメーションの一種になります。

今回は、scrollイベントを使った追従ナビゲーション、Intersection Observerを使った追従ナビゲーションの2パターンで作成してみました。

完成版1(scroll イベントでの実装)

完成版2(Intersection Observer での実装)

実装手順 HTML / CSS

まずはマークアップから準備します。
マークアップはscrollイベントを使用した例、Intersection Observerを使用した例ともに同じものを使用します。

手順1(CSS / フェードイン用のCSS classの作成)

HTMLはサンプルとして用意したものとなりますので、詳しい説明は割愛します。
CSSについては、JavaScript側でスクロールに合わせて、アニメーションさせたいHTML要素に対してフェードイン用のclassを付け外しする処理を実行するため、そのフェードイン用のclassも合わせて準備しておきます。

今回は、アニメーションを適用するHTML要素を.elementとし、Styleを設定します。
フェードインアニメーションを適用する要素は、初期状態(アニメーションが適用される前)はopacity: 0;が適用されているため、非表示となっています。

また、transformプロパティを用いて、要素を下方向に移動しておき、transitionプロパティを用いて、アニメーションの期間や速度を調整します。

.element {
  width: 200px;
  height: 200px;
  background-color: #46e678;
  margin-top: 80vh;
  margin-inline: auto;

  transition: opacity 0.7s, transform 0.7s;
  transform: translateY(150px);
  opacity: 0;
}

.is-fadein {
  transform: translateX(0);
  opacity: 1;
}

実装手順 JavaScript(scrollイベント編)

手順2(JavaScript / スクロール量に合わせてclass名を追加する処理)

デバイス画面の高さをwindowHeight、縦方向のスクロールをlastScrollY、スクロールイベントの連続発火を防ぐ(負荷を抑える)ためのフラグをisTicking、スクロールされたときに生じるアニメーションがかかる要素の上端の位置をtargetPositionというローカル変数でそれぞれ定義します。

スクロール量が要素がデバイス画面の下端から100px上に来た時にclassを追加するための条件を設定します。
isInviewというローカル変数にスクロールアニメーション発火の判定範囲を真偽値として格納しておき、classList.toggleでこの真偽値を元にクラスを付与・削除する処理を追加します。

document.addEventListener('DOMContentLoaded', () => {
  const targetElements = document.querySelectorAll('.element')
  // 該当の要素が存在しなければ処理を終了
  if (!targetElements) return

  // ブラウザの内側の高さ
  let windowHeight = window.innerHeight
  // スクロール量
  let lastScrollY = window.scrollY
  // スクロールイベントの連続発火を防ぐためのフラグ
  let isTicking = false

  // 画面リサイズ時にwindowHeightを更新
  window.addEventListener('resize', () => {
    windowHeight = window.innerHeight
  })

  // 要素が表示された時にクラスを付与する関数
  const inviewAnimation = () => {
    targetElements.forEach((element) => {
      const targetPosition = element.getBoundingClientRect().top + lastScrollY
      const isInview = lastScrollY > targetPosition - windowHeight + 100
      element.classList.toggle('is-fadein', isInview)
    })
  }

  // スクロールイベント発火時に実行する関数
  const onScroll = () => {
    // inviewAnimationを実行中は処理をスキップ
    if (isTicking) return
    lastScrollY = window.scrollY
    isTicking = true
    // 1フレーム待ってからinviewAnimationを実行
    requestAnimationFrame(() => {
      inviewAnimation()
      isTicking = false
    })
  }

  // スクロールイベントを登録
  window.addEventListener('scroll', onScroll)
})

これで、scrollイベントを使ったフェードインアニメーションの完成です!
スクロールで指定された位置に要素が入った時に、opacityプロパティによって非表示になっていた要素にclass名が付き、要素がふわっと表示されるようになります。

実装手順 JavaScript (Intersection Observer編)

手順2(JavaScript / スクロール量に合わせてclass名を追加する処理)

続きまして、Intersection Observerを使った実装の解説を行います。
(ユーザーがスクロールを行うたびに発生するscrollイベントに対して、要素を監視し、要素が画面に入ったことで実行されるIntersection Observerの方がパフォーマンスが高く、現在主流とされています。)

Intersection Observerのoptionを、optionsというローカル変数(オブジェクト)に格納しておきます。
今回は初期値をそのまま引用しました。それぞれのoptionの中身としては、

  • root: ビューポートを基準にするためnullを設定。
  • rootMargin: 追加のマージンを設定しないため0pxを設定。
  • threshold: 要素が10%表示されたときにコールバックを呼び出すため0.1を設定。

といった設定を行なっています。

// Intersection Observerの option
const options = { root: null, rootMargin: '0px', threshold: 0.1 }

IntersectionObserverのコールバックの引数に渡されるIntersectionObserverEntryオブジェクトのプロパティは、IntersectionObserverのrootに対象要素が交差しているかどうかを真偽値で判定するプロパティです。
entry.isIntersectingtrueの場合、要素がビューポート内で交差していることを意味します。
この真偽値を使用して、要素がビューポートと交差していない場合は、.is-fadeinを削除、交差している場合は.is-fadeinを追加する処理を記述します。

記述下部のforEach文では、.elementクラスを持つすべての要素を監視しています。
この記述により、要素がrootの範囲に入ったかどうかが判定され、class名の付け替えが実行されることにより、フェードインアニメーションを適用できます。

document.addEventListener('DOMContentLoaded', () => {
  const options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.1
  };
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
    entry.target.classList.toggle('is-fadein', entry.isIntersecting);
    });
  }, options);
  document.querySelectorAll('.element').forEach(element => {
    observer.observe(element);
  });
});

要素のアニメーションはCSSのtransitionを、表示タイミングは、scrollイベントの場合はtargetPositionを、Intersection Observerの場合は、optionを調整することでコントロールが可能になるので、デザインの要件に合わせて調整してみてください。

参考文献

JavaScriptのIntersection Observerでスクロールに合わせてグラデーションの色を変更する

【JavaScript】スクロールでふわっと表示(フェードイン)させる方法