UI

Parallax

Hover me
"use client"

import * as React from "react"

import { cn } from "@/lib/utils"
import { ExampleProps } from "@/types/type"

function throttle<T extends (...args: any[]) => any>(
  func: T,
  delay: number
): (...args: Parameters<T>) => void {
  let lastCall = 0
  return (...args: Parameters<T>) => {
    const now = new Date().getTime()
    if (now - lastCall < delay) {
      return
    }
    lastCall = now
    return func(...args)
  }
}

export const Parallax = ({ className, children }: ExampleProps) => {
  const [rotate, setRotate] = React.useState({ x: 0, y: 0 })

  //using usecallback here to mitigate eslint warning
  //usecallback(fn, deps) is equivalent to usememo(() => fn, deps).
  const onMouseMove = React.useMemo(
    () =>
      throttle((e: React.MouseEvent<HTMLDivElement>) => {
        const card = e.currentTarget
        const box = card.getBoundingClientRect()
        const x = e.clientX - box.left
        const y = e.clientY - box.top
        const centerX = box.width / 2
        const centerY = box.height / 2
        const rotateX = (y - centerY) / 4
        const rotateY = (centerX - x) / 4

        setRotate({ x: rotateX, y: rotateY })
      }, 100),
    []
  )

  const onMouseLeave = () => {
    setRotate({ x: 0, y: 0 })
  }

  return (
    <div
      className={cn(
        "relative size-full rounded-xl transition-[all_400ms_cubic-bezier(0.03,0.98,0.52,0.99)_0s] will-change-transform",
        className
      )}
      onMouseMove={onMouseMove}
      onMouseLeave={onMouseLeave}
      style={{
        transform: `perspective(1000px) rotateX(${rotate.x}deg) rotateY(${rotate.y}deg) scale3d(1, 1, 1)`,
        transition: "all 400ms cubic-bezier(0.03, 0.98, 0.52, 0.99) 0s"
      }}
    >
      {children}
    </div>
  )
}