'use client' import React, { useEffect, useRef, RefObject } from 'react' import { gsap } from 'gsap' const lerp = (a: number, b: number, n: number): number => (1 - n) * a + n * b const getMousePos = (e: MouseEvent, container?: HTMLElement | null): { x: number; y: number } => { if (container) { const bounds = container.getBoundingClientRect() return { x: e.clientX - bounds.left, y: e.clientY - bounds.top, } } return { x: e.clientX, y: e.clientY } } interface CrosshairProps { color?: string containerRef?: RefObject | null } const Crosshair: React.FC = ({ color = 'white', containerRef = null }) => { const cursorRef = useRef(null) const lineHorizontalRef = useRef(null) const lineVerticalRef = useRef(null) const filterXRef = useRef(null) const filterYRef = useRef(null) let mouse = { x: 0, y: 0 } useEffect(() => { const handleMouseMove = (ev: Event) => { const mouseEvent = ev as MouseEvent mouse = getMousePos(mouseEvent, containerRef?.current || undefined) if (containerRef?.current) { const bounds = containerRef.current.getBoundingClientRect() if ( mouseEvent.clientX < bounds.left || mouseEvent.clientX > bounds.right || mouseEvent.clientY < bounds.top || mouseEvent.clientY > bounds.bottom ) { gsap.to([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), { opacity: 0 }) } else { gsap.to([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), { opacity: 1 }) } } } const target: HTMLElement | Window = containerRef?.current || window target.addEventListener('mousemove', handleMouseMove) const renderedStyles: { [key: string]: { previous: number; current: number; amt: number } } = { tx: { previous: 0, current: 0, amt: 0.15 }, ty: { previous: 0, current: 0, amt: 0.15 }, } gsap.set([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), { opacity: 0 }) const onMouseMove = (ev: Event) => { const mouseEvent = ev as MouseEvent mouse = getMousePos(mouseEvent, containerRef?.current || undefined) renderedStyles.tx.previous = renderedStyles.tx.current = mouse.x renderedStyles.ty.previous = renderedStyles.ty.current = mouse.y gsap.to([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), { duration: 0.9, ease: 'Power3.easeOut', opacity: 1, }) requestAnimationFrame(render) target.removeEventListener('mousemove', onMouseMove) } target.addEventListener('mousemove', onMouseMove) const primitiveValues = { turbulence: 0 } const tl = gsap .timeline({ paused: true, onStart: () => { if (lineHorizontalRef.current) { lineHorizontalRef.current.style.filter = 'url(#filter-noise-x)' } if (lineVerticalRef.current) { lineVerticalRef.current.style.filter = 'url(#filter-noise-y)' } }, onUpdate: () => { if (filterXRef.current && filterYRef.current) { filterXRef.current.setAttribute('baseFrequency', primitiveValues.turbulence.toString()) filterYRef.current.setAttribute('baseFrequency', primitiveValues.turbulence.toString()) } }, onComplete: () => { if (lineHorizontalRef.current) lineHorizontalRef.current.style.filter = 'none' if (lineVerticalRef.current) lineVerticalRef.current.style.filter = 'none' }, }) .to(primitiveValues, { duration: 0.5, ease: 'power1', startAt: { turbulence: 1 }, turbulence: 0, }) const enter = () => tl.restart() const leave = () => { tl.progress(1).kill() } const render = () => { renderedStyles.tx.current = mouse.x renderedStyles.ty.current = mouse.y for (const key in renderedStyles) { const style = renderedStyles[key] style.previous = lerp(style.previous, style.current, style.amt) } if (lineHorizontalRef.current && lineVerticalRef.current) { gsap.set(lineVerticalRef.current, { x: renderedStyles.tx.previous }) gsap.set(lineHorizontalRef.current, { y: renderedStyles.ty.previous }) } requestAnimationFrame(render) } const links: NodeListOf = containerRef?.current ? containerRef.current.querySelectorAll('a') : document.querySelectorAll('a') links.forEach(link => { link.addEventListener('mouseenter', enter) link.addEventListener('mouseleave', leave) }) return () => { target.removeEventListener('mousemove', handleMouseMove) target.removeEventListener('mousemove', onMouseMove) links.forEach(link => { link.removeEventListener('mouseenter', enter) link.removeEventListener('mouseleave', leave) }) } }, [containerRef]) return (
) } export default Crosshair