You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 16:57:29