"use client"; import { useEffect, useRef, useState } from "react"; const DEFAULT_THRESHOLD = [0, 0.01, 0.1, 0.25, 0.5, 0.75]; export function useScrollSpy(sectionIds: string[], options?: IntersectionObserverInit) { const [activeId, setActiveId] = useState(sectionIds[0] ?? ""); const ratiosRef = useRef>(new Map()); const activeIdRef = useRef(activeId); useEffect(() => { activeIdRef.current = activeId; }, [activeId]); const threshold = options?.threshold ?? DEFAULT_THRESHOLD; const thresholdKey = Array.isArray(threshold) ? threshold.join("|") : String(threshold); useEffect(() => { const els = sectionIds .map((id) => document.getElementById(id)) .filter(Boolean) as HTMLElement[]; if (!els.length) return; const DEFAULT_ROOT_MARGIN = "83px 0px -65% 0px"; // 초기값 세팅(안정화) ratiosRef.current.clear(); els.forEach((el) => ratiosRef.current.set(el.id, 0)); const io = new IntersectionObserver( (entries) => { for (const e of entries) { const id = (e.target as HTMLElement).id; ratiosRef.current.set(id, e.isIntersecting ? e.intersectionRatio : 0); } // ✅ 전체 섹션 중 가장 ratio 큰 항목을 active로 let bestId = activeIdRef.current; let bestRatio = -1; for (const [id, ratio] of ratiosRef.current.entries()) { if (ratio > bestRatio) { bestRatio = ratio; bestId = id; } } if (bestId) setActiveId(bestId); }, { root: options?.root ?? null, threshold, rootMargin: options?.rootMargin ?? DEFAULT_ROOT_MARGIN, } ); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, [sectionIds, options?.root, options?.rootMargin, threshold, thresholdKey]); return activeId; }