React.jsでスクロールアニメーション!Intersection Observerで「要素が見えたら」を実装する
こんにちは。
食費を抑えるコツは、「タダ飯」だと最近気づいた、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をLaravelで作ってみた【システム解説あり】