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

如何在React中正确添加窗口滚动事件监听器实现元素动画

解决React导航栏滚动时Logo宽度变化的问题

原代码存在的问题

  • 直接操作DOM(document.querySelector("#logo"))不符合React状态驱动UI的设计理念,易引发状态与UI不一致的问题
  • useEffect依赖scrollY状态,每次滚动更新scrollY都会重复绑定/解绑滚动事件,造成不必要的性能损耗
  • 滚动事件触发过于频繁,未做节流处理,可能影响页面运行性能

优化方案

  1. 用React状态控制UI:定义isScrolled状态标记是否滚动超过200px,通过状态控制Logo样式,替代直接操作DOM
  2. 优化事件绑定逻辑:仅在组件挂载时绑定一次滚动事件,卸载时解绑,将useEffect依赖项设为空数组
  3. 添加节流处理:限制滚动事件的触发频率,降低性能消耗
  4. 用useRef保存节流函数:避免组件重渲染时重复创建函数

优化后的完整代码

import { useState, useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import logo from "../assets/images/logo.png";

import "../styles/navbar.scss";

const Navbar = () => {
  const [isScrolled, setIsScrolled] = useState(false);
  const throttledScrollHandler = useRef(null);

  const handleScroll = () => {
    setIsScrolled(window.scrollY > 200);
  };

  useEffect(() => {
    // 实现节流:100ms内仅执行一次滚动逻辑
    throttledScrollHandler.current = throttle(handleScroll, 100);
    
    window.addEventListener("scroll", throttledScrollHandler.current);

    // 组件卸载时解绑事件
    return () => {
      window.removeEventListener("scroll", throttledScrollHandler.current);
    };
  }, []);

  // 节流函数封装
  function throttle(func, delay) {
    let lastCall = 0;
    return function(...args) {
      const now = Date.now();
      if (now - lastCall >= delay) {
        func.apply(this, args);
        lastCall = now;
      }
    };
  }

  return (
    <header>
      <nav>
        <Link to="/" className="nav-link">
          Grupuri
        </Link>
        <Link to="/" className="nav-link">
          Despre noi
        </Link>
        <Link to="/" className="nav-link">
          Live
        </Link>
      </nav>
      {/* 根据状态动态设置Logo宽度,添加过渡动画提升体验 */}
      <img 
        src={logo} 
        alt="Maranata Lupeni" 
        id="logo" 
        style={{ width: isScrolled ? "0" : "150px", transition: "width 0.3s ease" }}
      />
      <nav>
        <Link to="/" className="nav-link">
          Rugaciune
        </Link>
        <Link to="/" className="nav-link">
          Credinta
        </Link>
        <Link to="/" className="nav-link">
          Contact
        </Link>
      </nav>
    </header>
  );
};

export default Navbar;

额外优化建议

可以将样式逻辑迁移到SCSS文件中,通过类名切换控制Logo宽度,实现JS与样式的解耦:

#logo {
  width: 150px;
  transition: width 0.3s ease;
}

#logo.scrolled {
  width: 0;
}

对应JSX修改为:

<img 
  src={logo} 
  alt="Maranata Lupeni" 
  id="logo" 
  className={isScrolled ? "scrolled" : ""}
/>

内容的提问来源于stack exchange,提问作者Alin-Alexandru Peter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:33:18