109 lines
2.6 KiB
TypeScript
109 lines
2.6 KiB
TypeScript
'use client'
|
|
import { useRef, useEffect } from 'react'
|
|
import { gsap } from 'gsap'
|
|
|
|
interface MagneticButtonProps {
|
|
children: React.ReactNode
|
|
className?: string
|
|
href?: string
|
|
onClick?: () => void
|
|
strength?: number
|
|
}
|
|
|
|
export default function MagneticButton({
|
|
children,
|
|
className = '',
|
|
href,
|
|
onClick,
|
|
strength = 0.3
|
|
}: MagneticButtonProps) {
|
|
const buttonRef = useRef<HTMLElement>(null)
|
|
const textRef = useRef<HTMLSpanElement>(null)
|
|
|
|
useEffect(() => {
|
|
const button = buttonRef.current
|
|
const text = textRef.current
|
|
if (!button || !text) return
|
|
|
|
// Check for reduced motion
|
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
if (prefersReducedMotion) return
|
|
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
const rect = button.getBoundingClientRect()
|
|
const x = e.clientX - rect.left - rect.width / 2
|
|
const y = e.clientY - rect.top - rect.height / 2
|
|
|
|
gsap.to(button, {
|
|
x: x * strength,
|
|
y: y * strength,
|
|
duration: 0.3,
|
|
ease: 'power2.out'
|
|
})
|
|
|
|
gsap.to(text, {
|
|
x: x * strength * 0.5,
|
|
y: y * strength * 0.5,
|
|
duration: 0.3,
|
|
ease: 'power2.out'
|
|
})
|
|
}
|
|
|
|
const handleMouseLeave = () => {
|
|
gsap.to([button, text], {
|
|
x: 0,
|
|
y: 0,
|
|
duration: 0.5,
|
|
ease: 'elastic.out(1, 0.3)'
|
|
})
|
|
}
|
|
|
|
const handleMouseDown = () => {
|
|
gsap.to(button, {
|
|
scale: 0.98,
|
|
duration: 0.1,
|
|
ease: 'power2.out'
|
|
})
|
|
}
|
|
|
|
const handleMouseUp = () => {
|
|
gsap.to(button, {
|
|
scale: 1,
|
|
duration: 0.2,
|
|
ease: 'power2.out'
|
|
})
|
|
}
|
|
|
|
button.addEventListener('mousemove', handleMouseMove)
|
|
button.addEventListener('mouseleave', handleMouseLeave)
|
|
button.addEventListener('mousedown', handleMouseDown)
|
|
button.addEventListener('mouseup', handleMouseUp)
|
|
|
|
return () => {
|
|
button.removeEventListener('mousemove', handleMouseMove)
|
|
button.removeEventListener('mouseleave', handleMouseLeave)
|
|
button.removeEventListener('mousedown', handleMouseDown)
|
|
button.removeEventListener('mouseup', handleMouseUp)
|
|
}
|
|
}, [strength])
|
|
|
|
const Component = href ? 'a' : 'button'
|
|
|
|
return (
|
|
<Component
|
|
ref={buttonRef as any}
|
|
href={href}
|
|
onClick={onClick}
|
|
className={`relative inline-block cursor-pointer ${className}`}
|
|
style={{ willChange: 'transform' }}
|
|
>
|
|
<span
|
|
ref={textRef}
|
|
className="relative inline-block"
|
|
style={{ willChange: 'transform' }}
|
|
>
|
|
{children}
|
|
</span>
|
|
</Component>
|
|
)
|
|
} |