🌊
React+IntersectionObserverでスクロールエフェクトを実装
スクロールしたらフェードインする、よくあるスクロールエフェクトをReact環境で実装してみました。
基本はVanilla-JSでやっていたような動きをReactで実現するような実装となっています。
CSSライブラリはemotionを使っています。
フェードイン前、フェードイン後の共通CSSを設定
今回は下から上にフェードする動きをつけるので、共通のCSSとして定義しておきます。
export const beforeFadeIn = css`
opacity: 0;
transform: translateY(20px);
transition: transform 0.5s ease-in-out, opacity 0.5s ease-in-out;
`
export const afterFadeIn = css`
opacity: 1;
transform: translateY(0);
`
フェードインの処理をカスタムフックとして設定
各コンポーネントで処理を共通化するためにカスタムフックとして切り出します。
IntersectionObserverを設定し、イベントが発火した際にisVisibleがtrueになるようにする。
一度だけ発火させたいため、isIntersecting
がtrueの場合のみisVisible
をtrueにする。
引数としてthreshold
とrootMargin
を設定できるようにしています。
指定がなければデフォルト値を適用するようにしています。
// useFadeIn.ts
import type { RefObject } from 'react'
import { useEffect, useRef, useState } from 'react'
interface UseFadeInOptions {
threshold?: number
rootMargin?: string
}
export const useFadeIn = <T extends HTMLElement>(
options?: UseFadeInOptions
): { isVisible: boolean; targetRef: RefObject<T> } => {
const [isVisible, setIsVisible] = useState(false)
const targetRef = useRef<T>(null)
useEffect(() => {
const currentRef = targetRef.current
const observer = new IntersectionObserver(
([entry]) => {
if (entry && entry.isIntersecting) {
setIsVisible(true)
observer.unobserve(entry.target)
}
},
{
root: null,
rootMargin: options?.rootMargin || '-20% 0px -20% 0px',
threshold: options?.threshold || 0,
}
)
if (currentRef) {
observer.observe(currentRef)
}
return () => {
if (currentRef) {
observer.unobserve(currentRef)
}
}
}, [options, isVisible])
return { isVisible, targetRef }
}
各コンポーネントに設定
各コンポーネントにカスタムフックと共通のCSSを読み込んで、フェードインさせる要素に指定します。
css部分はemotionを使ってフェード前にbeforeFadeIn
を指定し、フェード後はafterFadeIn
を指定しています。
import { useFadeIn } from '@/hooks/useFadeIn'
import { afterFadeIn, beforeFadeIn } from '@/styles/function'
export const Sample = (props: SampleProps) => {
const { isVisible, targetRef } = useFadeIn<HTMLParagraphElement | HTMLDivElement | HTMLHeadingElement>()
return (
<div css={styles.container}>
<div css={styles.titleWrap}>
<p css={[styles.subTitle, beforeFadeIn, isVisible && afterFadeIn]} ref={targetRef}>
{props.subTitle}
</p>
<h2 css={[styles.title, beforeFadeIn, isVisible && afterFadeIn]} ref={targetRef}>
{props.title}
</h2>
</div>
</div>
)
}
スクロールに応じてフェードする機能は、多くのコンポーネントで共通の設定が求められることが多いです。そのため、カスタムフックとCSSを共通化することで、管理が効率的に行えるようになりました。
Discussion