🚴‍♂️

Reactのスクロールアニメーションを「react-intersection-observer」を使って実現する

2023/01/14に公開

Reactを使ったスクロールアニメーションの便利な方法を見つけたので備忘録として残します。

経緯

スクロールアニメーションをvanillaJSで実現する際、getElementByIDやquerySelectorALLなどで要素を取得しループさせて記述させることが多いかと思いますが、Reactではそのようなやり方は非推奨みたいです。

なので最初はuseRefなどを使ってできないか頑張ってみましたが、うまくできず悩みました、、

そんな時に、「Intersection Observer API」がreact用に用意されていることを知ったので「これだ!」と思い、実装してみました。
ちなみにIntersection Observer APIとは、scrollイベントでスクロール量を計ったりせずに、要素との交差を判定してイベントを発火させることができるものです。
スクロールのたびに発火させないので、パフォーマンスが優れていると言われています。

詳しい解説は下記の記事が分かりやすいかと思います。
JSでのスクロール連動エフェクトには Intersection Observerが便利

実装方法

今回は、「react-intersection-observer」というプラグインを使用して、フェードイン(画面にふわっと現れる)ものを作成したいと思います。

流れはとても簡単です。

  • フェードイン用のコンポーネントを作成して、propsとしてchildrenを受け取る
  • propsで受け取ったchildrenに対してアニメーションを適用

下記がコンポーネントの完成版です。
開発環境はNext.js、TypeScriptでCSSはstyled-componentを使用しています。

FadeAnimation.tsx
import { ReactNode } from "react";
import { useInView } from "react-intersection-observer";
import styled from "styled-components";

//TypeScriptの型定義 (今回は無視してOK)
type fadeAnimationType = {
  children: ReactNode;
};

export const FadeAnimation = (props: fadeAnimationType) => {
  const { children } = props;

  /**
   * スクロールイベントのオプション
   * 「ref」検知する要素
   * 「inView」要素が見えたかどうか(見えたらtrue)
   * 「rootMargin」要素の検知の「余白」を設定
   * 「triggerOnce」検知を一度だけ行うかどうか
   */
  const { ref, inView } = useInView({
    rootMargin: "-100px",
    triggerOnce: true,
  });

  return (
    /**
     * ★スクロールさせたい要素を囲む
     * refで指定すると対象の要素になる
     * inViewのtrueかfalseを受け取り、styled-componentに渡す
     */
    <SFadeElem inView={inView} ref={ref}>
      {children}
    </SFadeElem>
  );
};

/**
 * ★inView(trueかfalse)で受け取り、それに応じてcssを切り替える
 */
const SFadeElem = styled.span<{ inView: boolean }>`
  display: inline-block;
  transition: opacity 0.5s cubic-bezier(0.47, 0, 0.745, 0.715);
  
  //opacityをtrueなら1、falseなら0とする
  opacity: ${(props) => (props.inView ? 1 : 0)};
`;

補足ですが、「SFadeElem」とはstyled-componentで使うためのタグとなっており、実態としてはspanタグです。
なのでchildrenをspanタグで囲っているということになります。

あとはアニメーションを適用させたい要素を作成したFadeAnimationで囲めば適用されます。

Test.tsx
import { FadeAnimation } from "./fadeAnimation";

export const Test = () => {
  return (
    <FadeAnimation>
      <p>アニメーションを適用する要素</p>
    </FadeAnimation>
  );
};

いかがだったでしょうか?
intersection-observerの設定自体では4行程度で終わりますし、設定方法も簡単だったかと思います。
Reactで開発する際はぜひ使ってみてください。

Discussion