複数要素に対応したIntersection Observerのカスタムフック実装
はじめに
複数の要素に対してIntersection Observerで同じ挙動をさせるのにカスタムフックがあると便利だなと思ったので実装してみました。もちろん、単体の要素にも使えます。React + TypeScriptで実装してます。
Intersection Observer とは
ページをスクロールして要素がビューポート内に表示されたタイミングで要素をふわっと表示させるアニメーションをさせたい場合などに使います。
交差オブザーバー API (Intersection Observer API) は、ターゲットとなる要素が、祖先要素または文書の最上位のビューポートと交差する変化を非同期的に監視する方法を提供します。
カスタムフックの実装
import { RefObject, useEffect } from 'react'
/**
* @param refs Intersection Observer を適用した動作をさせたい要素の RefObject の配列
* @param callback IntersectionObserver のインスタンス生成時に渡すコールバック関数
* @param options IntersectionObserver のインスタンス生成時に渡すオプション
*/
export const useIntersectionObserver = (
refs: RefObject<HTMLElement>[],
callback: (entries: IntersectionObserverEntry[]) => void,
options?: IntersectionObserverInit
): void => {
useEffect(() => {
const observer = new IntersectionObserver(callback, options)
refs.forEach((ref) => {
if (ref.current) {
observer.observe(ref.current)
}
})
return () => {
refs.forEach((ref) => {
if (ref.current) {
observer.unobserve(ref.current)
}
})
}
})
}
実装解説
export const useIntersectionObserver = (
refs: RefObject<HTMLElement>[],
callback: (entries: IntersectionObserverEntry[]) => void,
options?: IntersectionObserverInit
): void => { ...... }
第一引数refs
には Intersection Observer を適用した動作をさせたい要素のRefObject
を配列で渡します。ここで渡した要素全てに対して同じ動作をさせることができます。
第二引数callback
と第三引数options
は IntersectionObserver のインスタンス生成時に渡す引数です。つまり、第二引数には条件(要素がビューポート内に入ったなど)を満たした時に実行したい処理を関数で渡し、第三引数にはその条件を指定します。
export const useIntersectionObserver = (
......
): void => {
useEffect(() => {
const observer = new IntersectionObserver(callback, options)
refs.forEach((ref) => {
if (ref.current) {
observer.observe(ref.current)
}
})
return () => {
refs.forEach((ref) => {
if (ref.current) {
observer.unobserve(ref.current)
}
})
}
})
}
関数の処理としては、受け取ったRefObject
の配列から要素を1つずつ取り出してobserve
してコンポーネントがアンマウントする時にunobserve
するシンプルな実装になってます。
カスタムフックの使い方
任意の要素がビューポートに入ったタイミングでふわっと表示する実装は以下の通りです。
import { useIntersectionObserver } from '@/hooks/use-intersection-observer'
import { FC, useRef } from 'react'
import styles from './Component.module.scss'
// カスタムフックに渡すコールバック関数
const showElements = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// IntersectionObserver で設定された条件を満たした時に実行する処理
// 要素に active クラスを適用する
entry.target.classList.add(styles.active)
}
})
}
const Component: FC = () => {
const ref1 = useRef<HTMLHeadingElement>(null)
const ref2 = useRef<HTMLHeadingElement>(null)
const ref3 = useRef<HTMLHeadingElement>(null)
// カスタムフックを呼ぶ
useIntersectionObserver([ref1, ref2, ref3], showElements)
return (
<>
<div>↓ scroll ↓</div>
<h2 className={styles.heading} ref={ref1}>
Hello 1
</h2>
<h2 className={styles.heading} ref={ref2}>
Hello 2
</h2>
<h2 className={styles.heading} ref={ref3}>
Hello 3
</h2>
</>
)
}
export default Component
.heading {
color: #0bd;
font-size: 4rem;
font-weight: bold;
margin: 400px 0;
opacity: 0;
transform: translateX(-100px);
}
.active {
opacity: 1;
transition: 1s;
transform: translateX(0);
}
実装の意図
Intersection Observer のインスタンスを生成する時に渡すコールバック関数とオプションをカスタムフックの引数にして外から注入できる仕様にすることで IntersectionObserver を使った動作や動作タイミングを外側からコントロール可能にしています。
参考文献
Discussion