diff --git a/src/components/OnboardingTour.tsx b/src/components/OnboardingTour.tsx index c1cef066..5fca5491 100644 --- a/src/components/OnboardingTour.tsx +++ b/src/components/OnboardingTour.tsx @@ -280,6 +280,14 @@ export default function OnboardingTour() { return () => clearTimeout(t); }, [measureTarget]); + const updatePosition = useCallback(() => { + const el = document.getElementById(TOUR_STEPS[stepIndex]?.targetId ?? ""); + if (el) { + const r = el.getBoundingClientRect(); + setTargetRect({ top: r.top, left: r.left, width: r.width, height: r.height }); + } + }, [stepIndex]); + // Measure target whenever step changes (skip on first render — init effect handles that) useEffect(() => { if (!visible) return; @@ -328,25 +336,25 @@ export default function OnboardingTour() { }; }, [stepIndex, visible, measureTarget, dismiss, currentStep]); - // Re-measure on resize or scroll so spotlight stays anchored to target. - // requestAnimationFrame prevents layout thrashing on rapid scroll/resize events. + // Re-measure on resize and scroll useEffect(() => { if (!visible) return; - let rafId: number; - const remeasure = () => { - cancelAnimationFrame(rafId); - rafId = requestAnimationFrame(() => { - measureTarget(TOUR_STEPS[stepIndex]?.targetId ?? "").then(setTargetRect); - }); + + let frame: number; + const handleUpdate = () => { + cancelAnimationFrame(frame); + frame = requestAnimationFrame(updatePosition); }; - window.addEventListener("resize", remeasure); - window.addEventListener("scroll", remeasure, true); + + window.addEventListener("resize", handleUpdate); + window.addEventListener("scroll", handleUpdate, true); // true for capturing phase to catch all scrolls + return () => { - cancelAnimationFrame(rafId); - window.removeEventListener("resize", remeasure); - window.removeEventListener("scroll", remeasure, true); + window.removeEventListener("resize", handleUpdate); + window.removeEventListener("scroll", handleUpdate, true); + cancelAnimationFrame(frame); }; - }, [visible, stepIndex, measureTarget]); + }, [visible, updatePosition]); // Keyboard support useEffect(() => {