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

React自定义onScroll滚动动画异常:仅首个section触发时机正常

问题原因与解决方案

问题根源

你当前的触发条件sectionTop < triggerPoint存在逻辑缺陷:section.getBoundingClientRect().top是元素顶部到视口顶部的距离,对于页面下方的section来说,它们的初始sectionTop等于前面所有section的高度总和,只有当滚动距离足够大,让这个值小于triggerPoint时才会触发动画。这就导致后面的section需要滚动更多距离(甚至过半)才能满足条件,而第一个后续section(Clients)因为紧跟Home,滚动少量距离就能满足sectionTop < triggerPoint,所以看起来“按时触发”。

另外,你只在组件挂载时计算一次triggerPoint,如果窗口大小变化,这个值不会更新,也会导致触发时机偏差。


解决方案

方案1:修复Scroll事件逻辑

调整触发条件,确保section进入指定视口区域时就触发,同时添加窗口大小变化监听:

import './App.css';
import Home from './components/Sections/Home';
import Clients from './components/Sections/Clients';
import AboutUs from './components/Sections/AboutUs';
import Career from './components/Sections/Career';
import Discover from './components/Sections/Discover';
import OurWork from './components/Sections/OurWork';
import Testimonial from './components/Sections/Testimonial';
import Plan from './components/Sections/Plan';
import Footer from './components/Sections/Footer';
import { useEffect } from 'react';

function App() {

  useEffect(() => {
    let triggerPoint = window.innerHeight / 5 * 4;
    const triggerElement = document.querySelector(".trigger");
    triggerElement.style.top = triggerPoint + "px";

    const handleScroll = () => checkSections(triggerPoint);
    const handleResize = () => {
      triggerPoint = window.innerHeight / 5 * 4;
      triggerElement.style.top = triggerPoint + "px";
      checkSections(triggerPoint);
    };

    window.addEventListener("scroll", handleScroll);
    window.addEventListener("resize", handleResize);

    checkSections(triggerPoint);

    return () => {
      window.removeEventListener("scroll", handleScroll);
      window.removeEventListener("resize", handleResize);
    };

  }, []);

  function checkSections(triggerPoint){
    const sections = document.querySelectorAll("section");
    
    sections.forEach(section => {
      const rect = section.getBoundingClientRect();
      // 当section顶部到达triggerPoint位置,且未完全滚出视口时触发
      const shouldTrigger = rect.top <= triggerPoint && rect.bottom > 0;
      
      if(shouldTrigger){
        section.classList.add("on-scroll");
      }else{
        section.classList.remove("on-scroll")
      }
    })
  }

  return (
    <div id="App" style={{position: "relative"}}>
      <div className='trigger' style={{position: "fixed", left: "100px", background: "white", zIndex: "99", width: "50px", height: "50px"}}></div>
      <Home />
      <Clients />
      <AboutUs />
      <Career />
      <Discover />
      <OurWork />
      <Testimonial />
      <Plan />
      <Footer />
    </div>
  );
}

export default App;

方案2:改用Intersection Observer API(推荐)

这是现代浏览器提供的更高效的元素可见性监听方案,无需手动处理scroll事件,逻辑更简洁:

import './App.css';
import Home from './components/Sections/Home';
import Clients from './components/Sections/Clients';
import AboutUs from './components/Sections/AboutUs';
import Career from './components/Sections/Career';
import Discover from './components/Sections/Discover';
import OurWork from './components/Sections/OurWork';
import Testimonial from './components/Sections/Testimonial';
import Plan from './components/Sections/Plan';
import Footer from './components/Sections/Footer';
import { useEffect } from 'react';

function App() {

  useEffect(() => {
    let triggerPoint = window.innerHeight / 5 * 4;
    const triggerElement = document.querySelector(".trigger");
    triggerElement.style.top = triggerPoint + "px";

    // 创建观察者,设置触发时机为section顶部到达triggerPoint位置
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          entry.target.classList.toggle("on-scroll", entry.isIntersecting);
        });
      },
      {
        rootMargin: `-${window.innerHeight - triggerPoint}px 0 0 0`,
        threshold: 0
      }
    );

    // 监听所有section
    const sections = document.querySelectorAll("section");
    sections.forEach(section => observer.observe(section));

    // 窗口大小变化时更新触发点
    const handleResize = () => {
      triggerPoint = window.innerHeight / 5 * 4;
      triggerElement.style.top = triggerPoint + "px";
      // 重新配置观察者
      observer.disconnect();
      observer.rootMargin = `-${window.innerHeight - triggerPoint}px 0 0 0`;
      sections.forEach(section => observer.observe(section));
    };

    window.addEventListener("resize", handleResize);

    return () => {
      observer.disconnect();
      window.removeEventListener("resize", handleResize);
    };

  }, []);

  return (
    <div id="App" style={{position: "relative"}}>
      <div className='trigger' style={{position: "fixed", left: "100px", background: "white", zIndex: "99", width: "50px", height: "50px"}}></div>
      <Home />
      <Clients />
      <AboutUs />
      <Career />
      <Discover />
      <OurWork />
      <Testimonial />
      <Plan />
      <Footer />
    </div>
  );
}

export default App;

内容的提问来源于stack exchange,提问作者Zohaib Mushtaq

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 11:49:53