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

React中如何获取div滚动位置并设置state同步菜单状态

实现方案

监听window滚动事件失效的核心原因是:独立开启内部滚动的div的滚动事件不会冒泡到window对象,必须直接对这个滚动容器本身绑定监听,配合各内容区块的位置计算,才能实现滚动时同步更新菜单激活状态。

具体修改点

  • 用useRef获取滚动容器.contentScrollDiv的DOM实例,避免直接操作document查询
  • 在useEffect生命周期中给滚动容器绑定scroll事件,组件卸载时必须移除监听防止内存泄漏
  • 补全所有内容区块的对应id,当前代码里workhistory/certifications/education三个区块都没设置id,不仅位置计算失效,原有锚点跳转也会出问题
  • 移除菜单项里嵌套的非法<a>标签,HTML规范不允许a标签嵌套,直接用外层a标签做跳转即可
  • 滚动触发时计算每个区块相对于滚动容器的偏移量,判断当前滚动位置匹配的区块索引,更新menuIndex状态

修正后代码

App.js

import "./App.scss";
import { useState, useEffect, useRef } from 'react';

function App() {
  const [menuIndex, setMenuIndex] = useState(0);
  // 滚动容器ref
  const scrollContainerRef = useRef(null);
  // 区块id和菜单索引的映射
  const sectionList = [
    { id: 'summery', index: 0 },
    { id: 'workhistory', index: 1 },
    { id: 'certifications', index: 2 },
    { id: 'education', index: 3 }
  ];

  const menuIndexHandler = (index) => {
    setMenuIndex(index)
  }

  // 滚动事件处理逻辑
  const handleScroll = () => {
    const container = scrollContainerRef.current;
    if (!container) return;
    const scrollTop = container.scrollTop;
    // 从后往前遍历区块,匹配当前激活项
    for (let i = sectionList.length - 1; i >= 0; i--) {
      const section = document.getElementById(sectionList[i].id);
      if (!section) continue;
      // 减去30px偏移做提前触发,滚动体验更自然
      const offsetTop = section.offsetTop - container.offsetTop - 30;
      if (scrollTop >= offsetTop) {
        setMenuIndex(sectionList[i].index);
        break;
      }
    }
  };

  useEffect(() => {
    const container = scrollContainerRef.current;
    if (!container) return;
    // 绑定滚动监听
    container.addEventListener('scroll', handleScroll);
    // 组件卸载时移除监听
    return () => {
      container.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return (
    <div className="App">
      <div className="innerBox">
        <div className="menu">
          <div className="spaceHorizontal"></div>
   
          <a href="#summery">
            <div className={`menuItem  ${(menuIndex == 0) ? ' menuOpen' : ' menuClosed'} `} onClick={() =>menuIndexHandler(0)} >
              <label htmlFor="Summery Menu Label">             
                <div className="menuOpenAnimation"><span className="menuLabel">Summery</span></div>
              </label>
            </div>
          </a>
          <a href="#workhistory">
            <div className={`menuItem  ${(menuIndex == 1) ? ' menuOpen' : ' menuClosed'} `} onClick={() =>menuIndexHandler(1)}>
              <label htmlFor="Work History Menu Label">
                <div className="menuOpenAnimation"><span className="menuLabel">Work History </span></div>
              </label>
            </div>
          </a>
          <a href="#certifications">
            <div className={`menuItem  ${(menuIndex == 2) ? ' menuOpen' : ' menuClosed'} `} onClick={() =>menuIndexHandler(2)}>
              <label htmlFor="Certification Menu Label">
                <div className="menuOpenAnimation"><span className="menuLabel">Certifications</span></div>
              </label>
            </div>
          </a>
          <a href="#education">
            <div className={`menuItem  ${(menuIndex == 3) ? ' menuOpen' : ' menuClosed'} `} onClick={() =>menuIndexHandler(3)}>
              <label htmlFor="Education Menu Label">
                <div className="menuOpenAnimation"><span className="menuLabel">Education</span></div>
              </label>
            </div>
          </a>
        </div>
        <div className="content">
          <div className="spaceHorizontal">
              <h1>Digital Resume</h1>
          </div>
          {/* 给滚动容器绑定ref */}
          <div className="contentScrollDiv" ref={scrollContainerRef}>
            <div id="summery" className="contentSubSection">
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
            </div>
            {/* 补全缺失的区块id */}
            <div id="workhistory" className="contentSubSection">
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
            </div>
            <div id="certifications" className="contentSubSection">
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
            </div>
            <div id="education" className="contentSubSection">
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
              <p> Placeholder Text </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

App.scss

原有样式基本可以直接复用,只需要微调菜单文字标签的样式适配即可:

@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap');

.App {
  background-color: #202020;
  height: 100vh;
  width: 100vw;
  display: flex;
  font-family: 'Raleway', sans-serif;
}

.backgroundParticles{
  z-index: 1;
}

.innerBox {
  width: 900px;
  height: 700px; 
  margin: auto;
  display: flex;
  flex-direction: row;
  z-index: 2;
}

.spaceHorizontal{
  height: 50px;
  padding-bottom: 25px;
}

.menu {
  background-color: #3F485F;
  height: 600px;
  width: 200px;
  display: flex;
  flex-direction: column;
  border-radius: 25px 0px 0px 25px;  
  
}

.menuItem {
  height: 40px;
  width: 164px;
  border-bottom: 1px solid white;
}

.menuLabel{
  z-index: 3;
  color: white;
  margin-top: 15px;
  padding-left: 5px;
  display: block;
}

.menuOpenAnimation{
  z-index: 2;
  width: 164px;
  height: 40px;
  position: absolute;
}

@keyframes menuOpenAnimation{
  from {background-color: rgb(131, 197, 190);
        width: 0px;}
  to {background-color: #BC7528;
       width: 164px;}
}

.menuOpen {
  animation-name: menuOpenAnimation;
  animation-duration: 0.5s;
  animation-fill-mode: forwards;  
  animation-timing-function: ease-out;
  background-color: #49B095;
}

.content {
  background-color: #F8F7F1;
  height: 600px;
  width: 100%;
  display: flex;
  flex-direction: column;
  border-radius: 0px 25px 25px 0px; 
}

h1 {
  text-align: center;
}

hr {
  width: 100%;
}

.contentScrollDiv {
  height: 100%;
  overflow-y: scroll;
  scroll-behavior: smooth;
  padding-inline: 25px;
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.contentScrollDiv::-webkit-scrollbar {
  display: none;
}

a {
  color: black;
  text-decoration: none;
}

优化提示

如果页面内容很长、滚动频繁,可以给handleScroll方法加节流处理,降低状态更新频率,避免不必要的性能损耗。


内容的提问来源于stack exchange,提问作者Azhar Razak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 21:36:26