michaelpeskov/components/MagneticButton.tsx

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>
)
}