対象の要素がスクロールしたピクセル数を測るhooksを紹介します。どのように動作するかは以下の例を確認ください。
対象の要素の左端からどれほどスクロールされたか、上端からどれほどスクロールされたかを受け取れます。
まずは、hooksの外形を作ります。ただ初期値{ x: 0, y: 0 }
を返すだけのhooksを作ります。
type Position = {
x: number
y: number
};
const useScroll = (): Position => {
const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
return position;
};
Position
という型で座標を表す型を作成して、useState
で状態として提供する形です。
次に、スクロール位置を取得する方法を考えます。
スクロール位置はWeb APIのElement
のscrollTop
、scrollLeft
プロパティから取得可能です。そして、スクロール位置をposition
に反映するのはスクロール単位で行いたいです。
つまり、対象の要素のscroll
イベントが発火したタイミングで対象の要素のscrollTop
、scrollLeft
プロパティでposition
を更新することで達成できます。
それを反映したのが以下のコードです。
type Position = {
x: number;
y: number;
};
const initPosition: Position = { x: 0, y: 0 };
const useScroll = (ref: RefObject<HTMLElement>): Position => {
const [position, setPosition] = useState<Position>(initPosition);
useEffect(() => {
const { current } = ref;
if (current === null) {
setPosition(initPosition);
return;
}
const handleScroll = () => {
const { scrollTop, scrollLeft } = current;
setPosition({ x: scrollLeft, y: scrollTop });
};
current.addEventListener("scroll", handleScroll);
return () => {
current.removeEventListener("scroll", handleScroll);
};
});
return position;
};
useScroll
の引数にref
を追加しました。これは以下のような型を持っています。
{
readonly current: HTMLElement | null;
}
そして、対象の要素のscroll
イベントに動作を付与したいのでuseEffect
を用いて副作用を記述しました。
エフェクト関数は、ref
からcurrent
を取り出してnull
の時は初期化を行います。これは、対象の要素が削除されたり、作り直されたりする場合にposition
が前回の値を保つことを防いでいます。
HTMLElement
の時は対象の要素があるとして、scroll
イベントにhandleScroll
を登録します。handleScroll
は対象の要素のscrollTop
、scrollLeft
プロパティでpositoin
を更新します。これで、スクロールのたびに`positionの値が更新されるようになりました。そして、それらはクリーンアップのタイミングで解除しています。
以上が対象の要素がスクロールしたピクセル数を測るhooksとなります。そのまま使うこともできますし、拡張することでさまざまな事象に対応することができるので活用してみてはいかがでしょうか。