import React, { useState, useEffect, useRef } from 'react'; const sections = [ { id: 'hero', label: 'Старт' }, { id: 'infra', label: 'ЦОД' }, { id: 'science', label: 'Наука' }, { id: 'social', label: 'Социум' }, { id: 'leadership', label: 'Рейтинг' }, { id: 'metrics', label: 'Цифры' }, { id: 'scaling', label: 'Масштаб' }, { id: 'team', label: 'Команда' }, ]; const Timeline: React.FC = () => { const [activeId, setActiveId] = useState('hero'); const observerRef = useRef(null); const visibilities = useRef>(new Map()); useEffect(() => { const handleIntersection = (entries: IntersectionObserverEntry[]) => { entries.forEach((entry) => { // Calculate approximate visible height in pixels // This creates a fair comparison between scrolling (tall) sections and snapping (short) sections const visibleHeight = entry.intersectionRect.height; visibilities.current.set(entry.target.id, visibleHeight); }); let maxVisibleHeight = 0; let maxId = ''; // The active section is the one occupying the most vertical space in the viewport for (const [id, height] of visibilities.current.entries()) { if (height > maxVisibleHeight) { maxVisibleHeight = height; maxId = id; } } if (maxId && maxVisibleHeight > 0) { setActiveId(maxId); } }; const options = { root: null, // viewport rootMargin: '0px', threshold: Array.from({ length: 11 }, (_, i) => i * 0.1), }; observerRef.current = new IntersectionObserver(handleIntersection, options); // Observe all sections sections.forEach(({ id }) => { const element = document.getElementById(id); if (element) observerRef.current?.observe(element); }); return () => observerRef.current?.disconnect(); }, []); const handleClick = (e: React.MouseEvent, id: string) => { e.preventDefault(); const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth' }); } }; return (
{sections.map((item) => { const isActive = activeId === item.id; return ( handleClick(e, item.id)} className="group flex flex-row-reverse items-center justify-end gap-3" > ); }; export default Timeline;