22
33import { useEffect , useRef , type HTMLAttributes } from 'react' ;
44
5+ import { hasFinePointer } from '@/lib/media' ;
6+ import { rafThrottle } from '@/lib/rafThrottle' ;
7+
58/**
69 * Publishes the pointer's position over the hovered `[data-spotlight]` descendant
710 * as `--mx` / `--my`, in px relative to that element's own box.
@@ -25,9 +28,8 @@ export default function SpotlightGroup({
2528 if ( ! root ) return ;
2629
2730 // A spotlight is a hover affordance; a coarse pointer has nothing to track.
28- if ( ! window . matchMedia ( '(hover: hover) and (pointer: fine)' ) . matches ) return ;
31+ if ( ! hasFinePointer ( ) ) return ;
2932
30- let frame = 0 ;
3133 let target : HTMLElement | null = null ;
3234 let clientX = 0 ;
3335 let clientY = 0 ;
@@ -37,13 +39,12 @@ export default function SpotlightGroup({
3739 el ?. style . removeProperty ( '--my' ) ;
3840 } ;
3941
40- const apply = ( ) => {
41- frame = 0 ;
42+ const apply = rafThrottle ( ( ) => {
4243 if ( ! target ) return ;
4344 const rect = target . getBoundingClientRect ( ) ;
4445 target . style . setProperty ( '--mx' , `${ clientX - rect . left } px` ) ;
4546 target . style . setProperty ( '--my' , `${ clientY - rect . top } px` ) ;
46- } ;
47+ } ) ;
4748
4849 const onMove = ( event : PointerEvent ) => {
4950 const next = ( event . target as Element | null ) ?. closest < HTMLElement > ( '[data-spotlight]' ) ?? null ;
@@ -56,7 +57,7 @@ export default function SpotlightGroup({
5657
5758 clientX = event . clientX ;
5859 clientY = event . clientY ;
59- if ( ! frame ) frame = requestAnimationFrame ( apply ) ;
60+ apply ( ) ;
6061 } ;
6162
6263 const onLeave = ( ) => {
@@ -70,7 +71,7 @@ export default function SpotlightGroup({
7071 return ( ) => {
7172 root . removeEventListener ( 'pointermove' , onMove ) ;
7273 root . removeEventListener ( 'pointerleave' , onLeave ) ;
73- if ( frame ) cancelAnimationFrame ( frame ) ;
74+ apply . cancel ( ) ;
7475 } ;
7576 } , [ ] ) ;
7677
0 commit comments