LoginSignup
1
1

対象の要素がスクロールしたピクセル数を測るuseScrollを作成する

Posted at

対象の要素がスクロールしたピクセル数を測る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のElementscrollTopscrollLeftプロパティから取得可能です。そして、スクロール位置をpositionに反映するのはスクロール単位で行いたいです。
つまり、対象の要素のscrollイベントが発火したタイミングで対象の要素のscrollTopscrollLeftプロパティで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は対象の要素のscrollTopscrollLeftプロパティでpositoinを更新します。これで、スクロールのたびに`positionの値が更新されるようになりました。そして、それらはクリーンアップのタイミングで解除しています。

以上が対象の要素がスクロールしたピクセル数を測るhooksとなります。そのまま使うこともできますし、拡張することでさまざまな事象に対応することができるので活用してみてはいかがでしょうか。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1