Closed2
スクロール位置に応じて要素のサイズをなめらかに変える
フォントサイズや要素の幅・高さをスクロール量に応じてなめらかに変えたいというケースがごく稀にある。
上の例では最初は60pxだが、現在の位置より上にスクロールされると少しずつサイズが小さくなり、要素がビューポート上部に達したときに20pxになるように実装されている。
これをReactで実装したのだが、結局使わなかったので供養しておく。
1. 要素の位置を取得するカスタムフックを作る
複数の要素に対して同じことをやりたくなったときのために、要素の位置を取得する処理はカスタムフックに切り出しておく。
useOffsetTop.tsx
import React, { useEffect, useState, useCallback } from "react";
export function useOffsetTop(ref?: React.RefObject<HTMLElement>) {
const [viewportOffsetTop, setViewportOffsetTop] = useState<number | undefined>(undefined);
const [pageOffsetTop, setPageOffsetTop] = useState<number | undefined>(
undefined
);
const handler = useCallback(() => {
if (!ref?.current) return;
const clientRect = ref.current.getBoundingClientRect();
setViewportTop(clientRect.top);
const newPageOffsetTop = clientRect.top + window.pageYOffset;
if (newPageOffsetTop !== pageOffsetTop) setPageOffsetTop(newPageOffsetTop);
}, [ref]);
useEffect(() => {
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
}, [ref, handler]);
return { viewportTop, pageOffsetTop };
}
- 引数には位置を調べたい要素のRefObjectを渡す。
- 返り値の
viewportTop
は現在表示されているビューポートの上端を起点にしたオフセット位置を表す。 - スクロールで通りすぎたときには(ビューポート上端より上にある場合)はマイナスの値となる。
-
pageOffsetTop
はスクロール量に関係なく、ページ上端からの位置を表す。 - ここでは省略しているが、繰り返しレンダリングが発生することによる負荷を減らすために、
handler()
にはThrottleを設定して呼び出される頻度を減らすと良い
2. 位置に応じてCSSのプロパティを変える
ExampleHeader.tsx
import { useRef, useMemo } from "react";
const maxIconSize = 60; // 最大のアイコンサイズ
const minIconSize = 20; // 最小のアイコンサイズ
export const Icon: React.VFC = () => {
const iconRef = useRef(null);
const { pageOffsetTop, viewportTop } = useOffsetTop(iconRef);
// 要素の位置をもとにサイズを計算
const iconSize = useMemo(() => {
if (pageOffsetTop === undefined || viewportTop === undefined)
return maxIconSize;
const size = (viewportTop / pageOffsetTop) * maxIconSize;
if (size <= minIconSize) return minIconSize;
return size.toFixed(1);
}, [pageOffsetTop, viewportTop]);
return (
<div
className="icon"
ref={iconRef}
style={{
width: `${iconSize}px`,
height: `${iconSize}px`,
}}
/>
);
};
これで最初は要素の幅・高さは60px(maxIconSize
の値)だが、ビューポート上部に達したときには20px(minIconSize
の値)となる。その中間のスクロール位置ではスクロール量に応じた幅・高さとなる。
👆 ちなみにこの例では、CSSでposition: sticky
とtop: 0
を指定することでビューポート上部に達した後はページ上部に追尾されるようにしている。
このスクラップは2021/11/14にクローズされました