React-Intersection Observerについてメモ。
スクロールアニメーションで使ってみました!
Intersection ObserverはJavaScriptにある機能で、特定の要素がウィンドウ内に見えるようになったり、見えなくなったりを監視してくれます。ウィンドウ内に指定の要素が入るタイミングでCSSクラスを付与できるので、スクロールアニメーションの実装に役に立ちます。
jQueryでスクロールアニメーションを実装だと要素の位置までの距離を計算するので処理が重くなりコードも複雑になっていましたが、Intersection Observeならコードも簡潔になります。
今回は「react-intersection-observer」をインストールして使っています。
インストールはコマンドはこちらです。
npm install react-intersection-observer
それでは実装したコードの説明をしていきます!
FadeInアニメーションの実装
コードはこちらです。
スクロールで要素がウィンドウ内に入ったらふわっと表示されるスクロールアニメーションの実装しました。
import { ReactNode } from 'react';
import { useInView } from 'react-intersection-observer';
import './styles.scss';
type FadeAnimationProps = {
children: ReactNode;
};
const FadeAnimation = (props: FadeAnimationProps) => {
const { children } = props;
const { ref, inView } = useInView({
rootMargin: '0px',
triggerOnce: true,
});
return (
<div className={`fade-element ${inView ? 'visible' : ''}`} ref={ref}>
{children}
</div>
);
};
export default FadeAnimation;
useInViewなど気になる部分はありますが、細かな説明は一旦省きます。上記はシンプルなコンポーネントです。
{children}にすることでスクロールアニメーションをつけたいタグやコンポーネントを覆うだけで実装できます。
<div className={`fade-element ${inView ? 'visible' : ''}`} ref={ref}>
{children}
</div>
「inView」という判定を使ってWindow内に要素が入ったらvisibleクラスが付与されてアニメーションが開始されます。
次にこちらはアニメーションのcssです。
.fade-element {
opacity: 0;
}
.fade-element.visible {
animation-name: FadeIn;
animation-duration: 1s;
animation-fill-mode: forwards;
animation-timing-function: ease-out;
opacity: 0;
}
@keyframes FadeIn {
0% {
opacity: 0;
transform: translateY(150px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
ここまで出来たらFadeAnimationコンポーネントでアニメーションをつけたい要素を覆ってあげるだけです。
下記のようにすることで「AboutMe」コンポーネントやh1タグにスクロールアニメーションを実装しています。
import AboutMe from '../components/About/AboutMe';
import Mv from '../components/MainVisual/Mv';
import FadeAnimation from '../parts/ScrollAnimation/FadeAnimation.tsx';
export default function TopPage() {
return (
<>
<main>
<Mv />
<FadeAnimation>
<AboutMe />
</FadeAnimation>
<FadeAnimation>
<h1>覆ってあげるだけでアニメーション</h1>
</FadeAnimation>
</main>
</>
);
}
react-intersection-observerの使い方
ここからはコードの詳細に入っていきます。再び最初のコードに戻ります。
重要な部分はこちらです。
const { ref, inView } = useInView({
rootMargin: '0px',
triggerOnce: true,
});
return (
<div className={`fade-element ${inView ? 'visible' : ''}`} ref={ref}>
{children}
</div>
);
要素がビューポートに入ったどうかを監視する「useInView
フック」
監視対象には参照(ref
)を割り当てます。inVewはブール値になっており、要素がビューポート内にあるかどうかを示します。ビューポートに入った場合は「True」を返します。
これによって要素がビューポートに入ったタイミングで「visible」クラスを対象に付与にしています。
オプションについて
「rootMargin」は要素がビューポートに入るタイミングを調整します。
今回は0pxなので要素がビューポートに入った時、つまりブラウザ画面下に表示された時に実行されます。
100pxと設定した場合はビューポートに入る100px前で実行されます。
-100pxの時は要素がビューポートに完全に入った後で、さらに100px進んだ時に実行されます。
「triggerOnce」は要素がビューポートに入ったことを一度だけトリガーするかどうかを設定しています。今回はtrueにして一度だけ実行にしています。
画面に入るたびに何度もアニメーションをするとしつこい印象になってしまいますからね。
その他オプション
threshold
要素がビューポート内でどの程度見えるかを指定します。0.0から1.0の間の値を取ります。複数の値を指定することもできます。
const { ref, inView } = useInView({
threshold: 0.5, // 要素の50%が見えたらトリガー
});
// 複数のしきい値を指定する例
const { ref, inView } = useInView({
threshold: [0, 0.25, 0.5, 0.75, 1], // 段階的にトリガー
});
コールバック関数
onChangeを使うことでコールバック関数を使うことも出来ます。
import { useInView } from 'react-intersection-observer';
import './styles.css'; // スタイルシートを追加
const App = () => {
const { ref, inView } = useInView({
rootMargin: '0px',
threshold: 0.5,
onChange: (inView) => {
if (inView) {
console.log('Element is in view');
} else {
console.log('Element is out of view');
}
}
});
return (
<div>
<h1>Scroll down to see the element</h1>
<div style={{ height: '100vh' }}></div>
<div ref={ref} className={`box ${inView ? 'visible' : ''}`}>
{inView ? 'I am visible!' : 'I am not visible!'}
</div>
<div style={{ height: '100vh' }}></div>
</div>
);
};
export default App;