React实现页面滚动时Nav组件状态自动切换
解决滚动到组件区块时自动切换导航激活状态的问题
核心思路
把导航的activeNav状态从Nav组件提升到App组件,让App作为所有区块组件和Nav的父组件,统一接收各个区块的视口状态,同步更新导航的激活状态。
步骤1:修改App组件,管理全局激活状态
将Nav的状态移到App中,同时给每个区块组件传递更新激活状态的回调函数:
import { useState } from 'react'; import Langbar from './Langbar'; import Header from './Header'; import Nav from './Nav'; import About from './About'; import Education from './Education'; import Experience from './Experience'; import Projects from './Projects'; import Contact from './Contact'; import Footer from './Footer'; function App() { // 把activeNav状态提升到App组件 const [activeNav, setActiveNav] = useState('#'); return ( <div> <Langbar /> <Header /> {/* 把状态和更新函数传递给Nav */} <Nav activeNav={activeNav} setActiveNav={setActiveNav} /> {/* 给每个区块传递onEnterViewport回调,指定对应的导航ID */} <About onEnterViewport={() => setActiveNav('#about')} /> <Education onEnterViewport={() => setActiveNav('#education')} /> <Experience onEnterViewport={() => setActiveNav('#experience')} /> <Projects onEnterViewport={() => setActiveNav('#projects')} /> <Contact onEnterViewport={() => setActiveNav('#contact')} /> <Footer /> </div> ); }
步骤2:改造Nav组件,接收props
去掉Nav内部的状态,改用父组件传递的activeNav和setActiveNav:
const Nav = ({ activeNav, setActiveNav }) => { return ( <nav> <a href="#" onClick={() => setActiveNav('#')} className={activeNav === '#' ? 'active' : ''}><HiHome /></a> <a href="#about" onClick={() => setActiveNav('#about')} className={activeNav === '#about' ? 'active' : ''}><HiUser /></a> <a href="#education" onClick={() => setActiveNav('#education')} className={activeNav === '#education' ? 'active' : ''}><FaGraduationCap /></a> <a href="#experience" onClick={() => setActiveNav('#experience')} className={activeNav === '#experience' ? 'active' : ''}><HiBriefcase /></a> <a href="#projects" onClick={() => setActiveNav('#projects')} className={activeNav === '#projects' ? 'active' : ''}><FaLaptopCode /></a> <a href="#contact" onClick={() => setActiveNav('#contact')} className={activeNav === '#contact' ? 'active' : ''}><HiMailOpen /></a> </nav> ) }
步骤3:修正区块组件(以About为例)
调整之前的错误写法,正确传递视口触发回调:
require('intersection-observer'); import { handleViewport } from 'react-in-viewport'; const AboutRender = (props) => { const { inViewport, forwardedRef, onEnterViewport } = props; const { t } = useTranslation(); // 当组件进入视口时触发父组件传递的回调 if (inViewport) { onEnterViewport(); } return ( <section id="about" ref={forwardedRef}> {/* 原有的About内容保持不变 */} <h2>{t("about.title")}</h2> <div className="container about__container"> <div className="about__me"> <div className="about__me-image"> <img src={Me} alt="About image" /> </div> </div> <div className="about__content"> <h3>{t("about.summary")}</h3> <div className="about__content__text"> <p>{t("about.summary_1")}</p> <p>{t("about.summary_2")}</p> <p>{t("about.summary_3")}</p> <p>{t("about.summary_4")}</p> </div> <h3>{t("about.tech_skills")}</h3> <div className="about__content__skills"> <article className="skill__card"> <SiHtml5 className="skill__icon"/> <h5>HTML</h5> </article> <article className="skill__card"> <SiCss3 className="skill__icon"/> <h5>CSS</h5> </article> <article className="skill__card"> <SiJavascript className="skill__icon"/> <h5>JavaScript</h5> </article> <article className="skill__card"> <SiReact className="skill__icon"/> <h5>React, React Native</h5> </article> <article className="skill__card"> <SiFigma className="skill__icon"/> <h5>Figma</h5> </article> </div> </div> </div> </section> ) } // 配置intersection observer选项,设置组件进入视口的比例阈值 const AboutWithRef = handleViewport(AboutRender, { threshold: 0.5 // 当组件50%进入视口时触发 }); const About = ({ onEnterViewport }) => { return( <AboutWithRef onEnterViewport={onEnterViewport} /> ) }
额外优化:处理多区块同时在视口的情况
如果页面较短导致多个区块同时出现在视口中,可以调整阈值(比如设为0.8),确保只有当前主要展示的区块触发导航激活;也可以在App组件中添加逻辑,判断哪个区块的顶部距离视口最近,再更新激活状态。
内容的提问来源于stack exchange,提问作者Artur Sarhizov
相关产品推荐
相关产品推荐

