Reactでsticky状態に応じてスタイルを変える(javascript)

JavaScript

スクロール中に上下方に固定される要素があり、position: sticky;を利用して張り付き状態の時にbackgroundの色を変えるなど、変化をつけたい場合は多いです。
:hoverみたいな感じで:stickyという状態が取れればいいのですが、残念ながらそんな都合の良い指定はできません。

解決するにはintersectionObserverで要素が画面内にあるかないかを判定する必要があります。
今回は画面最下部にある要素が画面内にStickされた状態になった時、という場合のコードを記載します。

const StickyButton = () => {
  const stickyRef = useRef<HTMLDivElement>(null)
  const [isSticky, setIsSticky] = useState(false)

  useEffect(() => {
    const currentStickyRef = stickyRef.current

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry) {
          setIsSticky(!entry.isIntersecting)
        }
      },
      { threshold: [1] },
    )

    if (currentStickyRef) {
      observer.observe(currentStickyRef)
    }

    return () => {
      if (currentStickyRef) {
        observer.unobserve(currentStickyRef)
      }
    }
  }, [])

  return (
    <>
      <Stack
        position="sticky"
        bottom={0}
        transition="background-color .5s"
        bg={isSticky ? "rgba(255, 255, 255, 0.80)" : "rgba(255, 255, 255, 0)"}
      >
        <Button>test</Button>
      </Stack>
      <div ref={stickyRef} />
    </>
  )
}

Stack要素のbackgroundを変更するコードなのですが、キモは<div ref={stickyRef} />です。
今回は下方向張り付きなのでターゲットのさらに下部に幅も高さも0の要素を作成し、その要素が表示されていればsticky状態ではない、もしsticky状態ならこのdivは画面内には存在し得ない、ということを利用します。

stickyな要素そのものの状態で色々判断できるといいのですが、その場合には親子関係やら位置関係やらを厳密に考える必要が出てきます。その点div1つ増やし、そちらを判断の根拠にするだけで割とシンプルに判定が可能です。

ただし、これが最下端でなく、上下方向それぞれのスクロールにも対応する必要がある場合には複雑度が増しますのでご注意を。

コメント