From 8041c22c5ac8add0c7ead3306b1ed608934a4e45 Mon Sep 17 00:00:00 2001 From: fengsilin Date: Tue, 31 Mar 2026 17:36:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .../pages/Home/components/ScrollNavigator.jsx | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 web/src/pages/Home/components/ScrollNavigator.jsx diff --git a/web/src/pages/Home/components/ScrollNavigator.jsx b/web/src/pages/Home/components/ScrollNavigator.jsx new file mode 100644 index 0000000..110dda6 --- /dev/null +++ b/web/src/pages/Home/components/ScrollNavigator.jsx @@ -0,0 +1,90 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React, { useState, useEffect, useCallback } from 'react'; + +const ScrollNavigator = ({ sections }) => { + const [activeIndex, setActiveIndex] = useState(0); + + const getActiveSection = useCallback(() => { + const container = document.querySelector('.home-snap-container'); + if (!container) return 0; + + const scrollTop = container.scrollTop; + const containerHeight = container.clientHeight; + const scrollCenter = scrollTop + containerHeight * 0.4; + + let closestIndex = 0; + let closestDistance = Infinity; + + sections.forEach((section, index) => { + const el = document.getElementById(section.id); + if (el) { + const rect = el.getBoundingClientRect(); + const elCenter = rect.top + rect.height / 2 + scrollTop; + const distance = Math.abs(elCenter - scrollCenter); + + if (distance < closestDistance) { + closestDistance = distance; + closestIndex = index; + } + } + }); + + return closestIndex; + }, [sections]); + + useEffect(() => { + const container = document.querySelector('.home-snap-container'); + if (!container) return; + + const handleScroll = () => { + const newIndex = getActiveSection(); + setActiveIndex(newIndex); + }; + + container.addEventListener('scroll', handleScroll, { passive: true }); + handleScroll(); // 初始化 + + return () => container.removeEventListener('scroll', handleScroll); + }, [getActiveSection]); + + const scrollToSection = (id) => { + const el = document.getElementById(id); + if (el) { + el.scrollIntoView({ behavior: 'smooth' }); + } + }; + + return ( + + ); +}; + +export default ScrollNavigator;