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

Next.js/React中实现类似elsner.com的分段滚动功能问题

Next.js分段滚动操作时连续跳转多区块的问题修复

我在Next.js应用中尝试实现分段滚动功能,通过window.addEventListener监听滚轮、按键等事件来控制滚动。目前按键控制的效果符合预期,但使用滚轮或触摸滑动时,会出现连续跳转多个区块的问题,以下是我的初始实现代码:

const options = {
    root: null,
    rootMargin: '0px',
    threshold: 1.0,
};

const homeref = useRef(null);
const mainref = useRef(null);
const aboutref = useRef(null);
const servicesref = useRef(null);
const solutionsref = useRef(null);
const idoref = useRef(null);
const contactref = useRef(null);

const [mainactive, setmainactive] = useState('home');

const scrollto = (id) => {
    scroller.scrollTo(id, {
        duration: 1000,
        delay: 0,
        smooth: 'easeInOutQuart',
    });
};

const itemset = (id, scroll) => {
    setmainactive(id);
    scrollto(scroll);
    return;
};

const handleScroll = (direction) => {
    const sections = ['home', 'about', 'services', 'ido', 'solutions', 'contact'];
    const currentIndex = sections.indexOf(mainactive);
    let newIndex = currentIndex;

    if (direction === 'down') {
        if (mainactive === 'about') {
            const aboutBottom = aboutref.current.getBoundingClientRect().bottom;
            if (aboutBottom <= window.innerHeight) {
                newIndex = currentIndex < sections.length - 1 ? currentIndex + 1 : currentIndex;
            }
        } else {
            newIndex = currentIndex < sections.length - 1 ? currentIndex + 1 : currentIndex;
        }
    } else if (direction === 'up') {
        newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;
    }

    const nextSection = sections[newIndex];
    if (nextSection !== mainactive) {
        setmainactive(nextSection);
        scrollto(nextSection);
    }
};

useEffect(() => {
    const handleKeyDown = (event) => {

        const wheeldown = event.wheelDeltaY < 0 || event.deltaY > 0;
        const wheelup = event.wheelDeltaY > 0 || event.deltaY < 0;

        if (event.key === 'ArrowDown' || wheeldown || event.key === 'ArrowRight') {
            if ((mainactive === 'home' && window.scrollY > 0) || mainactive === 'about') {
                setmainactive('about');
                handleScroll('down');
            } else if (mainactive === 'about') {
                handleScroll('down');
            } else if (mainactive === 'services') {
                handleScroll('down');
            } else if (mainactive === 'ido') {
                handleScroll('down');
            }
        } else if (event.key === 'ArrowUp' || wheelup || 'ArrowLeft') {
            if (mainactive === 'services' || mainactive === 'ido' || mainactive == 'solutions') {
                handleScroll('up');
            } else if (mainactive === 'about') {
                setTimeout(() => {
                    setmainactive('home');
                }, 1000);
                scrollto('home');
            }
        }
    };

    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('wheel', handleKeyDown);
    window.addEventListener('touchmove', handleKeyDown, { passive: false });

    return () => {
        window.removeEventListener('keydown', handleKeyDown);
        window.removeEventListener('wheel', handleKeyDown);
    };
}, [mainactive]);

问题原因

滚轮和触摸滑动事件会连续触发多次(比如滚轮滚动一次会触发多个wheel事件),每次触发都会执行一次滚动跳转逻辑,导致连续跳过多个区块。此外,原代码中存在条件判断重复、ArrowLeft逻辑错误等问题,也会加剧异常行为。

修复方案

  1. 添加滚动锁定状态,在滚动动画过程中禁止触发新的滚动请求
  2. 修正事件处理中的逻辑错误
  3. 对滚轮/触摸事件添加防抖或直接阻止默认行为(在锁定状态下)

修改后的完整代码

import { useState, useRef, useEffect } from 'react';

const options = {
    root: null,
    rootMargin: '0px',
    threshold: 1.0,
};

const homeref = useRef(null);
const mainref = useRef(null);
const aboutref = useRef(null);
const servicesref = useRef(null);
const solutionsref = useRef(null);
const idoref = useRef(null);
const contactref = useRef(null);

const [mainactive, setmainactive] = useState('home');
// 新增:标记是否正在执行滚动动画
const [isScrolling, setIsScrolling] = useState(false);

const scrollto = (id) => {
    setIsScrolling(true);
    scroller.scrollTo(id, {
        duration: 1000,
        delay: 0,
        smooth: 'easeInOutQuart',
        // 滚动结束后解锁
        onComplete: () => {
            setIsScrolling(false);
        }
    });
};

const itemset = (id, scroll) => {
    setmainactive(id);
    scrollto(scroll);
};

const handleScroll = (direction) => {
    const sections = ['home', 'about', 'services', 'ido', 'solutions', 'contact'];
    const currentIndex = sections.indexOf(mainactive);
    let newIndex = currentIndex;

    if (direction === 'down') {
        if (mainactive === 'about') {
            const aboutBottom = aboutref.current?.getBoundingClientRect().bottom;
            if (aboutBottom <= window.innerHeight) {
                newIndex = currentIndex < sections.length - 1 ? currentIndex + 1 : currentIndex;
            }
        } else {
            newIndex = currentIndex < sections.length - 1 ? currentIndex + 1 : currentIndex;
        }
    } else if (direction === 'up') {
        newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;
    }

    const nextSection = sections[newIndex];
    if (nextSection !== mainactive && !isScrolling) {
        setmainactive(nextSection);
        scrollto(nextSection);
    }
};

useEffect(() => {
    const handleKeyDown = (event) => {
        // 如果正在滚动,直接阻止默认行为并返回
        if (isScrolling) {
            event.preventDefault();
            return;
        }

        const wheeldown = event.type === 'wheel' && (event.wheelDeltaY < 0 || event.deltaY > 0);
        const wheelup = event.type === 'wheel' && (event.wheelDeltaY > 0 || event.deltaY < 0);
        const touchDown = event.type === 'touchmove' && event.touches[0].clientY < event.changedTouches[0].clientY;
        const touchUp = event.type === 'touchmove' && event.touches[0].clientY > event.changedTouches[0].clientY;

        // 处理向下滚动/按键
        if (event.key === 'ArrowDown' || wheeldown || event.key === 'ArrowRight' || touchDown) {
            switch(mainactive) {
                case 'home':
                    if (window.scrollY > 0) {
                        handleScroll('down');
                    }
                    break;
                case 'about':
                case 'services':
                case 'ido':
                case 'solutions':
                    handleScroll('down');
                    break;
                default:
                    break;
            }
            // 阻止滚轮/触摸的默认滚动行为
            if (event.type === 'wheel' || event.type === 'touchmove') {
                event.preventDefault();
            }
        } 
        // 处理向上滚动/按键
        else if (event.key === 'ArrowUp' || wheelup || event.key === 'ArrowLeft' || touchUp) {
            switch(mainactive) {
                case 'about':
                    setIsScrolling(true);
                    scrollto('home');
                    setTimeout(() => {
                        setmainactive('home');
                        setIsScrolling(false);
                    }, 1000);
                    break;
                case 'services':
                case 'ido':
                case 'solutions':
                case 'contact':
                    handleScroll('up');
                    break;
                default:
                    break;
            }
            if (event.type === 'wheel' || event.type === 'touchmove') {
                event.preventDefault();
            }
        }
    };

    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('wheel', handleKeyDown, { passive: false });
    window.addEventListener('touchmove', handleKeyDown, { passive: false });

    return () => {
        window.removeEventListener('keydown', handleKeyDown);
        window.removeEventListener('wheel', handleKeyDown);
        window.removeEventListener('touchmove', handleKeyDown);
    };
}, [mainactive, isScrolling]);

关键修改点说明

  • 滚动锁定状态isScrolling:在开始滚动时设为true,滚动动画完成后通过onComplete回调重置为false,期间阻止任何新的滚动请求。
  • 修正事件判断逻辑:区分事件类型(按键/滚轮/触摸),修复原代码中ArrowLeft的逻辑错误(原代码中|| 'ArrowLeft'会始终为真)。
  • 阻止默认行为:对滚轮和触摸事件调用event.preventDefault(),避免浏览器原生滚动和自定义滚动冲突。
  • 简化条件判断:用switch代替重复的if-else,让逻辑更清晰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 13:05:55