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

ReactJS无滚动div的滚动方向检测与页面切换动画实现

实现鼠标滚轮触发的React + Framer Motion页面切换动画

核心思路

因为页面容器不可滚动,无法通过scroll事件检测滚动行为,所以改用监听**鼠标滚轮事件(wheel)**判断滚动方向,结合状态管理控制页面切换逻辑,再通过Framer Motion实现指定动画效果:

  1. 检测滚轮方向(向上/向下)
  2. 根据方向更新当前页面与上一页状态
  3. 触发动画:当前页上移并透明化,新页同步显示,原页透明后返回原位
  4. 用动画锁防止滚动触发多次叠加动画

完整代码实现

import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';

// 页面数据示例
const pages = [
  { id: 1, content: '首页', bgColor: '#f8f9fa' },
  { id: 2, content: '列表页', bgColor: '#e9ecef' },
  { id: 3, content: '详情页', bgColor: '#dee2e6' }
];

export default function ScrollPageTransition() {
  // 状态管理
  const [currentPage, setCurrentPage] = useState(0);
  const [prevPage, setPrevPage] = useState(0);
  const [isAnimating, setIsAnimating] = useState(false);
  const [isScrollDirectionUp, setIsScrollDirectionUp] = useState(false);

  // 监听鼠标滚轮事件
  useEffect(() => {
    const handleWheel = (e) => {
      // 动画进行中忽略滚轮事件,避免叠加
      if (isAnimating) return;

      // 判断滚动方向:deltaY < 0 为向上滚动,反之向下
      const directionUp = e.deltaY < 0;
      setIsScrollDirectionUp(directionUp);

      // 页面边界判断 + 切换逻辑
      if (directionUp && currentPage > 0) {
        setIsAnimating(true);
        setPrevPage(currentPage);
        setCurrentPage(prev => prev - 1);
      } else if (!directionUp && currentPage < pages.length - 1) {
        setIsAnimating(true);
        setPrevPage(currentPage);
        setCurrentPage(prev => prev + 1);
      }
    };

    window.addEventListener('wheel', handleWheel);
    return () => window.removeEventListener('wheel', handleWheel);
  }, [currentPage, isAnimating]);

  // 动画变体定义
  const pageExitVariant = {
    y: '-100%',
    opacity: 0,
    transition: { duration: 0.5 }
  };

  const pageReturnVariant = {
    y: 0,
    opacity: 1,
    transition: { duration: 0.5, delay: 0.5 }
  };

  const pageEnterVariant = {
    opacity: 1,
    transition: { duration: 0.5 }
  };

  return (
    <div style={{ 
      position: 'relative', 
      width: '100vw', 
      height: '100vh', 
      overflow: 'hidden',
      margin: 0,
      padding: 0
    }}>
      {/* 上一页(原页面):执行退出动画后返回原位 */}
      <motion.div
        key={pages[prevPage].id}
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: '3rem',
          backgroundColor: pages[prevPage].bgColor
        }}
        initial={{ y: 0, opacity: 1 }}
        animate={isAnimating ? pageExitVariant : pageReturnVariant}
        onAnimationComplete={() => {
          // 动画完成后解锁,允许下次滚动触发
          if (isAnimating) setIsAnimating(false);
        }}
      >
        {pages[prevPage].content}
      </motion.div>

      {/* 当前页(新页面):同步显示 */}
      <motion.div
        key={pages[currentPage].id}
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: '3rem',
          backgroundColor: pages[currentPage].bgColor
        }}
        initial={{ opacity: 0 }}
        animate={pageEnterVariant}
      >
        {pages[currentPage].content}
      </motion.div>
    </div>
  );
}

关键细节说明

  • 滚轮事件监听:使用wheel事件替代scroll事件,解决容器不可滚动时无法检测滚动的问题,通过deltaY值判断滚动方向
  • 动画锁:isAnimating状态确保同一时间只有一组动画执行,避免快速滚动导致的动画混乱
  • 动画时序:原页面退出动画(0.5s)完成后,延迟0.5s执行返回原位动画,新页面同步从透明变为可见,符合需求中的动画流程
  • 边界处理:限制页面切换范围,避免超出页面数组的索引边界

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 04:36:03