React.jsでスクロールアニメーション!Intersection Observerで「要素が見えたら」を実装する

Javascript Programming

こんにちは。
食費を抑えるコツは、「タダ飯」だと最近気づいた、Webプログラマーのあっきーです。

今回は、「React.jsのスクロール量に応じたアニメーションを実装しよう」です。

Webサイトによくある「要素が見えたらフェードインする」みたいなやつをReactでやっていきいます。

とりあえず結論

まずは実装例をお見せします。

ポイントはこんな感じ。

  • CSSアニメーションを作る
  • Intersection Observerで要素の検知
  • 検知した要素にclassをつける

それでは解説です。

CSSでアニメーションを作る

まずは、cssでアニメーションを作りましょう。
Animate.cssを使ってもいいですが、ここでは自分で実装します。


.fadeIn {
  animation: fadeIn 500ms linear both;
}

@keyframes fadeIn {
  0% {
    opacity: 0;
    transform: translateY(30px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

今回は下から上にフェードインするアニメーションを定義しました。

アニメーションはいろんなところで使うと思うのでグローバルにcssクラスを作っておくといいですね。
また、再生時間や変化の仕方などを個別で設定できるクラスも作っておくと自由度が広がります。

ライブラリreact-intersection-observerを使って要素の出現をチェックする

次に、アニメーションを適用できるコンポーネントを作成しておきます。
src/components/AnimationTrigger.tsxを作成しましょう。

中身はこんな感じになります。


import React, { ReactNode } from "react";
import { useInView } from "react-intersection-observer";

interface Props {
  children?: ReactNode;
  animation: string;
  startClass?: string;
  endClass?: string;
  rootMargin?: string;
  triggerOnce?: boolean;
  className?: string;
  style?: React.CSSProperties;
}

const AnimationTrigger = ({
  children,
  rootMargin = "100px",
  animation,
  startClass = "",
  endClass = "",
  triggerOnce = false,
  className,
  style
}: Props) => {
  const { ref, inView } = useInView({
    rootMargin: rootMargin,
    triggerOnce: triggerOnce
  });
  return (
    <div ref={ref} className={className} style={style}>
      <div className={inView ? animation : startClass}>{children}</div>
    </div>
  );
};

export default AnimationTrigger;

ここのポイントはreact-intersection-observerというライブラリを使って要素の検知を行っているところです。

react-intersection-observerの初期設定

まず、以下のコマンドでインストールします。

npm install react-intersection-observer

そして使いたいコンポーネントでまず初期設定を行います。


  const { ref, inView } = useInView({
    rootMargin: rootMargin,
    triggerOnce: triggerOnce
  });

このuseInViewで設定ができます。
返り値については次の通り。

  • ref: 検知する要素
  • inView: 要素が見えたかどうか(見えたらtrue

そして引数についてはこちら。

  • rootMarginM: 要素の検知の「余白」を設定。ここで検知の早めたり遅めたりできる
  • triggerOnce: 検知を一度だけ行うかどうか。trueで一度だけになる。

引数は親コンポーネントから値を渡して設定することで汎用的になります。

見えたらclassをつける

そしてHTML要素はどうなるかというと。


<div ref={ref} className={className} style={style}>
  <div className={inView ? animation : startClass}>{children}</div>
</div>

inViewがtrue、つまり要素が見えたらになったら、親コンポーネントから受け取ったクラスをつけます。
見えてなかったらstartClassというアニメーションが実行される前のクラスをつけておきます。

親コンポーネントから実際に動かす

アニメーション用のコンポーネントを作ったので、これを呼び出してみます。


import "./styles.css";
import AnimationTrigger from "./components/AnimationTrigger";

export default function App() {
  return (
    <div className="App">
      <div
        style={{
          height: "1000px",
          borderBottom: "1px solid #acacac"
        }}
      >
        スクロールしてみてください
      </div>
      <AnimationTrigger animation="fadeIn" rootMargin="100px" triggerOnce>
        <div
          style={{
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
            margin: "0 auto",
            height: "300px",
            width: "300px",
            boxShadow: "2px 3px 6px #333",
            background: "#f6f6f6"
          }}
        >
          スクロールするとフェードインされます。
          <br />
          フェードインされます
        </div>
      </AnimationTrigger>
    </div>
  );
}

<AnimationTrigger animation="fadeIn" rootMargin="-100px" triggerOnce>が重要ですね。
ここでアニメーションクラスなどを流しこんでいます。

animation="fadeIn"は「要素が検知されたら、フェードインするアニメーションを適用」を意味し、
rootMargin="-100px"は「検知されてから100px過ぎたら発火!」を意味し
triggerOnceは「一度だけ発火!」を意味します。

コンポーネントに流し込む値を変えればいろんな形でスクロールアニメーションを実装することができます。

scrollイベントを使わない方が良い

よくある解説だとscrollイベントで「上からどれだけスクロールされたか?」を考えて実装するのがあります。

これはReactに限らず、普通にJavascriptで実装する場合にもあまりよくないです。
理由は、スクロールするたびに実行されるのであまりパフォーマンスが良くないからです。

とくにReactの場合は、例えばさっきのようなコンポーネントをscrollイベントで作るとしますよね?
それをある1ページで5個呼び出すとします。

するとそのページで5重にスクロール検知が「常に」行われてしまいます。
明らかにパフォーマンスが下がりますよね。

Intersection Observerは要素の検知「のみ」を行うので、スクロール毎にイベントが発火されません。
なのでパフォーマンスが良いんですね。

なので今回はこちらを採用しました。

ということで、今日はここまで!
それでは、また。

スポンサードサーチ

オススメ英語学習用SNS "Our Dictionary"

人気記事英語学習用SNSをLaravelで作ってみた【システム解説あり】