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

React滚动位置触发事件失效求助:仅页面刷新时生效

解决滚动到指定位置触发事件无响应的问题

你的代码核心问题在于:useEffect的依赖数组为空,只会在组件首次挂载时执行一次,仅能在刷新页面时检查一次滚动位置;而滚动过程中不会重新执行位置判断逻辑,自然无法触发事件。以下是两种可行的解决方案:

方案一:滚动监听+状态标记

通过添加滚动监听函数,在滚动过程中持续检查元素位置,同时用useRef标记动画是否已触发,避免重复执行:

import { useEffect, useRef } from 'react';

// 组件内的useEffect代码
useEffect(() => {
    AOS.init();
    const cnt = document.querySelectorAll(".count")[props.num];
    const water = document.querySelectorAll(".water")[props.num];
    const Skills = document.querySelector('#skills');
    // 标记动画是否已执行,防止重复触发
    const animationTriggered = useRef(false);

    const handleScroll = () => {
        // 计算元素相对于文档顶部的位置
        const elementTop = Skills.offsetTop;
        // 滚动位置+视口高度,判断元素是否进入视口(可根据需求调整触发条件)
        const currentScroll = window.scrollY + window.innerHeight;

        if (currentScroll >= elementTop && !animationTriggered.current) {
            animationTriggered.current = true;
            let percent = 0; // 从0开始计数,避免初始innerText非数字的问题
            const interval = setInterval(() => {
                percent++;
                cnt.textContent = percent;
                water.style.transform = `translate(0, ${100 - percent}%)`;
                if (percent === props.percent) {
                    clearInterval(interval);
                }
            }, 60);
        }
    };

    // 首次挂载时先检查一次(防止页面加载时元素已在视口内)
    handleScroll();
    // 添加滚动监听
    window.addEventListener('scroll', handleScroll);

    // 清理函数:移除监听,避免内存泄漏
    return () => {
        window.removeEventListener('scroll', handleScroll);
    };
}, [props.num, props.percent]); // 依赖props变量,确保变化时重新初始化

关键优化点:

  • 新增scroll事件监听,滚动过程中持续触发位置检查
  • 用useRef存储触发状态,避免滚动时重复启动定时器
  • 修正元素位置计算逻辑,使用offsetTop获取稳定的文档相对位置
  • 在组件卸载时移除监听,防止内存泄漏
  • 依赖数组添加props.num和props.percent,确保props变化时重新执行逻辑

方案二:IntersectionObserver(更优性能)

使用浏览器原生的IntersectionObserverAPI,专门监听元素是否进入视口,性能比滚动监听更高效:

import { useEffect, useRef } from 'react';

// 组件内的useEffect代码
useEffect(() => {
    AOS.init();
    const cnt = document.querySelectorAll(".count")[props.num];
    const water = document.querySelectorAll(".water")[props.num];
    const Skills = document.querySelector('#skills');
    const animationTriggered = useRef(false);

    // 创建观察者实例
    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            // 元素进入视口且未触发过动画
            if (entry.isIntersecting && !animationTriggered.current) {
                animationTriggered.current = true;
                let percent = 0;
                const interval = setInterval(() => {
                    percent++;
                    cnt.textContent = percent;
                    water.style.transform = `translate(0, ${100 - percent}%)`;
                    if (percent === props.percent) {
                        clearInterval(interval);
                    }
                }, 60);
                // 触发后停止观察,节省性能
                observer.unobserve(entry.target);
            }
        });
    }, {
        threshold: 0.1 // 元素进入视口10%时触发,可自行调整比例
    });

    if (Skills) observer.observe(Skills);

    // 清理函数:停止观察
    return () => {
        if (Skills) observer.unobserve(Skills);
    };
}, [props.num, props.percent]);

优势:

  • 无需持续监听滚动事件,由浏览器原生处理元素可见性判断,性能更优
  • 可灵活配置触发条件(如元素进入视口的比例)
  • 触发后可主动停止观察,减少不必要的资源消耗

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 14:02:08