React & Framer Motionでスクロールに合わせてFadeInする汎用コンポーネントを作る

2024/04/22に公開

はじめに

React と Framer Motion を使用して、スクロールに合わせて FadeIn のアニメーションが発生する汎用コンポーネントを作成してみます。

また、1 つのコンポーネントが表示されたタイミングで他の要素も連続して表示されるようなアニメーションも実装します。

完成イメージ

スクロールに合わせて 連続して表示される
完成コード
'use client';

import { motion, useReducedMotion } from 'framer-motion';
import React, {
  ComponentPropsWithoutRef,
  createContext,
  useContext,
} from 'react';

const StaggerContext = createContext(false);
const viewport = { once: true, margin: '0px 0px -120px' };

// FadeInコンポーネント
export function FadeIn(props: ComponentPropsWithoutRef<typeof motion.div>) {
  const shouldReduceMotion = useReducedMotion();
  const isStagger = useContext(StaggerContext);

  return (
    <motion.div
      transition={{
        duration: 0.5,
      }}
      variants={{
        hidden: {
          opacity: 0,
          y: shouldReduceMotion ? 0 : 20,
        },
        visible: {
          opacity: 1,
          y: 0,
        },
      }}
      {...(isStagger
        ? {}
        : {
            initial: 'hidden',
            whileInView: 'visible',
            viewport,
          })}
      {...props}
    />
  );
}

// 連続して要素をFadeInするために上記のコンポーネントをラップするコンポーネント
export function FadeInWithStagger({
  slow = false,
  speed,
  ...props
}: ComponentPropsWithoutRef<typeof motion.div> & {
  slow?: boolean;
  speed?: number;
}) {
  return (
    <StaggerContext.Provider value>
      <motion.div
        initial="hidden"
        transition={{
          staggerChildren: speed ?? (slow ? 0.2 : 0.1),
        }}
        viewport={viewport}
        whileInView="visible"
        {...props}
      />
    </StaggerContext.Provider>
  );
}

実際に私のポートフォリオでも使用しています。
https://www.kurahashi.me/

FadeIn コンポーネント

まずは、スクロールに合わせて FadeIn するコンポーネントを作成します。

import { motion, useReducedMotion } from 'framer-motion';
import React, {
  ComponentPropsWithoutRef,
  createContext,
  useContext,
} from 'react';

// 連動させてFadeInするかどうかのコンテキスト
const StaggerContext = createContext(false);
// スクロールに合わせて表示するためのViewportの設定
const viewport = { once: true, margin: '0px 0px -120px' };

// FadeInコンポーネント
export function FadeIn(props: ComponentPropsWithoutRef<typeof motion.div>) {
  // アニメーションを減らすかどうか
  const shouldReduceMotion = useReducedMotion();

  // 連動させてFadeInするかどうか
  const isStagger = useContext(StaggerContext);

  return (
    <motion.div
      // アニメーションの設定
      transition={{
        duration: 0.5,
      }}
      // アニメーションのバリエーション
      variants={{
        hidden: {
          opacity: 0,
          y: shouldReduceMotion ? 0 : 20,
        },
        visible: {
          opacity: 1,
          y: 0,
        },
      }}
      // 連動させせない場合は、初期状態をhiddenにして、スクロールに合わせて表示する
      {...(isStagger
        ? {}
        : {
            initial: 'hidden',
            whileInView: 'visible',
            viewport,
          })}
      {...props}
    />
  );
}

motion.div に FadeIn の transitionvariants を設定しています。
また、useReducedMotion を使用して、ユーザーがシステムの設定でアニメーションを減らしている場合に対応しています。
isStagger は下記で作成する FadeInWithStagger コンポーネントで囲まれているかどうかを判定するためのコンテキストです。

FadeInWithStagger コンポーネント

次に、連続して要素を FadeIn するためのコンポーネントを作成します。

// 連続して要素をFadeInするために上記のコンポーネントをラップするコンポーネント
export function FadeInWithStagger({
  slow = false,
  speed,
  ...props
}: ComponentPropsWithoutRef<typeof motion.div> & {
  slow?: boolean;
  speed?: number;
}) {
  return (
    <StaggerContext.Provider value>
      <motion.div
        initial="hidden"
        // 子要素に対してのアニメーションの設定
        transition={{
          staggerChildren: speed ?? (slow ? 0.2 : 0.1),
        }}
        viewport={viewport}
        whileInView="visible"
        {...props}
      />
    </StaggerContext.Provider>
  );
}

FadeInWithStagger コンポーネントは、StaggerContext.Provider で囲まれた motion.div です。

StaggerContext.Provider で囲まれたFadeInコンポーネントは、isStaggertrue になるため、親要素の motion.div に設定された staggerChildren が適用されます。

使い方

FadeIn コンポーネントと FadeInWithStagger コンポーネントを使用して、スクロールに合わせて FadeIn するコンポーネントを作成します。

import { FadeIn, FadeInWithStagger } from 'components/FadeIn';

export default function Home() {
  return (

    // スクロールに合わせてFadeInする

    <FadeIn>
      <h1>タイトル</h1>
      <p>説明文</p>
    </FadeIn>

    // 連続して要素をFadeInする
    <FadeInWithStagger>
      <FadeIn>
        {...}
      </FadeIn>
      <FadeIn>
        {...}
      </FadeIn>
      <FadeIn>
        {...}
      </FadeIn>
    </FadeInWithStagger>
  );
}

まとめ

React と Framer Motion を使用して、スクロールに合わせて FadeIn するコンポーネントを作成しました。
簡単に FadeIn のアニメーションを実装できるため、ぜひ活用してみてください。

さいごに

もし、間違いや改善点があれば、コメントで教えていただけると嬉しいです。

ありがとうございました。

GitHubで編集を提案

Discussion