Add scroll-synced active state to post TOC

This commit is contained in:
2025-11-17 21:09:11 +08:00
parent c675fa65e3
commit b47549437a

View File

@@ -10,6 +10,7 @@ interface TocItem {
export function PostToc() { export function PostToc() {
const [items, setItems] = useState<TocItem[]>([]); const [items, setItems] = useState<TocItem[]>([]);
const [activeId, setActiveId] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
const headings = Array.from( const headings = Array.from(
@@ -23,6 +24,28 @@ export function PostToc() {
depth: el.tagName === 'H3' ? 3 : 2 depth: el.tagName === 'H3' ? 3 : 2
})); }));
setItems(mapped); setItems(mapped);
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = (entry.target as HTMLElement).id;
if (id) {
setActiveId(id);
}
}
});
},
{
// Trigger when heading is in upper 40% of viewport
rootMargin: '0px 0px -60% 0px',
threshold: 0.1
}
);
headings.forEach((el) => observer.observe(el));
return () => observer.disconnect();
}, []); }, []);
if (items.length === 0) return null; if (items.length === 0) return null;
@@ -37,7 +60,11 @@ export function PostToc() {
<li key={item.id} className={item.depth === 3 ? 'pl-3' : ''}> <li key={item.id} className={item.depth === 3 ? 'pl-3' : ''}>
<a <a
href={`#${item.id}`} href={`#${item.id}`}
className="line-clamp-2 hover:text-blue-600 dark:hover:text-blue-400" className={`line-clamp-2 hover:text-blue-600 dark:hover:text-blue-400 ${
item.id === activeId
? 'text-blue-600 dark:text-blue-400 font-semibold'
: ''
}`}
> >
{item.text} {item.text}
</a> </a>