React & Framer Motionでスクロールに合わせてFadeInする汎用コンポーネントを作る
はじめに
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>
);
}
実際に私のポートフォリオでも使用しています。
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 の transition
と variants
を設定しています。
また、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
コンポーネントは、isStagger
が true
になるため、親要素の 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 のアニメーションを実装できるため、ぜひ活用してみてください。
さいごに
もし、間違いや改善点があれば、コメントで教えていただけると嬉しいです。
ありがとうございました。
Discussion