use-in-viewport

PreviousNext

A hook to check if an element is in the viewport

Docs
hookshook

Preview

Loading preview…
registry/hooks/use-in-viewport.ts
import { useState } from 'react'
import { useEffectWithTarget } from '@/registry/hooks/use-effect-with-target'
import { getTargetElement } from '@/registry/lib/create-effect-with-target'
import type { BasicTarget } from '@/registry/lib/create-effect-with-target'

type CallbackType = (entry: IntersectionObserverEntry) => void

export interface Options {
  rootMargin?: string
  threshold?: number | number[]
  root?: BasicTarget<Element>
  callback?: CallbackType
}

export function useInViewport(
  target: BasicTarget | BasicTarget[],
  options?: Options,
) {
  const { callback, ...option } = options || {}

  const [state, setState] = useState<boolean>()
  const [ratio, setRatio] = useState<number>()

  useEffectWithTarget(
    () => {
      const targets = Array.isArray(target) ? target : [target]
      const els = targets
        .map((element) => getTargetElement(element))
        .filter(Boolean)

      if (!els.length) {
        return
      }

      const observer = new IntersectionObserver(
        (entries) => {
          for (const entry of entries) {
            setRatio(entry.intersectionRatio)
            setState(entry.isIntersecting)
            callback?.(entry)
          }
        },
        {
          ...option,
          root: getTargetElement(options?.root),
        },
      )

      els.forEach((el) => observer.observe(el!))

      return () => {
        observer.disconnect()
      }
    },
    [options?.rootMargin, options?.threshold, callback],
    target,
  )

  return [state, ratio] as const
}

Installation

npx shadcn@latest add @hooks/use-in-viewport

Usage

import { UseInViewport } from "@/hooks/use-in-viewport"
const value = UseInViewport()