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

React Router + Framer Motion:如何动态设置路由动画的初始/退出值?

解决方案

方案一:通过Context存储导航方向,动态控制动画参数

核心是在全局Context中记录用户的滚动导航方向(向下/向上),让每个路由组件根据方向动态计算动画的initial和exit值。

  1. 更新Context.js,添加导航方向状态:
import React, { useState, useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";

const Context = React.createContext();

function ContextProvider({ children }) {
  const navigate = useNavigate();
  const location = useLocation();
  const [navDirection, setNavDirection] = useState(null); // 存储'down'或'up'

  useEffect(() => {
    window.addEventListener("wheel", handleNavigation);
    return () => window.removeEventListener("wheel", handleNavigation);
  }, [location]);

  function handleNavigation(e) {
    if (location.pathname.includes("/work/")) return;
    
    const isScrollDown = e.deltaY > 1;
    setNavDirection(isScrollDown ? 'down' : 'up');

    if (isScrollDown && location.pathname === "/") {
      navigate("/skills");
    } else if (!isScrollDown && location.pathname === "/skills") {
      navigate("");
    } else if (isScrollDown && location.pathname === "/skills") {
      navigate("/aboutme");
    } else if (!isScrollDown && location.pathname === "/aboutme") {
      navigate("/skills");
    }
  }

  return <Context.Provider value={{ navDirection }}>{children}</Context.Provider>;
}

export { ContextProvider, Context };
  1. 修改Skills组件(Main和AboutMe同理适配):
import React, { useContext } from "react";
import { motion } from "framer-motion";
import { Context } from "./Context";
import { useLocation } from "react-router-dom";

export default function Skills() {
  const { navDirection } = useContext(Context);
  const location = useLocation();

  // 进入动画:从首页向下滚动来的,初始y=1000;从关于页向上滚动来的,初始y=-1000
  const initialY = navDirection === 'down' ? 1000 : -1000;
  // 退出动画:回退到首页(向上滚动)时y=1000;跳转到关于页(向下滚动)时y=-1000
  const exitY = location.pathname === '/skills' && navDirection === 'up' ? 1000 : -1000;

  return (
    <motion.div
      className="skills--container"
      initial={{ opacity: 0, y: initialY, transition: { duration: 0.8 } }}
      animate={{ opacity: 1, y: 0, transition: { duration: 0.8 } }}
      exit={{ opacity: 0, y: exitY, transition: { duration: 0.8 } }}
    >
      <p>Skills Page</p>
    </motion.div>
  );
}

方案二:利用React Router的state传递导航信息

在调用navigate时携带路由跳转的方向状态,目标路由组件通过useLocation获取该状态,动态设置动画参数。

  1. 更新Context.js中的handleNavigation方法:
function handleNavigation(e) {
  if (location.pathname.includes("/work/")) return;
  
  const isScrollDown = e.deltaY > 1;

  if (isScrollDown && location.pathname === "/") {
    navigate("/skills", { state: { from: 'down' } });
  } else if (!isScrollDown && location.pathname === "/skills") {
    navigate("", { state: { from: 'up' } });
  } else if (isScrollDown && location.pathname === "/skills") {
    navigate("/aboutme", { state: { from: 'down' } });
  } else if (!isScrollDown && location.pathname === "/aboutme") {
    navigate("/skills", { state: { from: 'up' } });
  }
}
  1. 修改Skills组件:
import React from "react";
import { motion } from "framer-motion";
import { useLocation } from "react-router-dom";

export default function Skills() {
  const location = useLocation();
  const fromDirection = location.state?.from;

  // 动态设置进入动画的y值
  const initialY = fromDirection === 'down' ? 1000 : -1000;

  return (
    <motion.div
      className="skills--container"
      initial={{ opacity: 0, y: initialY, transition: { duration: 0.8 } }}
      animate={{ opacity: 1, y: 0, transition: { duration: 0.8 } }}
      exit={({ target }) => {
        // 根据退出目标路由判断y值:回首页则y=1000,去关于页则y=-1000
        const isGoingToHome = target.pathname === '/';
        return { opacity: 0, y: isGoingToHome ? 1000 : -1000, transition: { duration: 0.8 } };
      }}
    >
      <p>Skills Page</p>
    </motion.div>
  );
}

更优方案:改用单页滚动实现(无需路由)

如果你的页面是线性的整页滚动结构,完全可以抛弃React Router,直接用Framer Motion实现单页滚动,动画控制会更简洁:

import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Main from "./Main";
import Skills from "./Skills";
import AboutMe from "./AboutMe";

const pageList = [
  { id: 'home', component: <Main /> },
  { id: 'skills', component: <Skills /> },
  { id: 'aboutme', component: <AboutMe /> }
];

export default function FullPageScroll() {
  const [currentIndex, setCurrentIndex] = useState(0);
  const [prevIndex, setPrevIndex] = useState(0);

  useEffect(() => {
    const handleWheel = (e) => {
      const isScrollDown = e.deltaY > 1;
      if (isScrollDown && currentIndex < pageList.length - 1) {
        setPrevIndex(currentIndex);
        setCurrentIndex(prev => prev + 1);
      } else if (!isScrollDown && currentIndex > 0) {
        setPrevIndex(currentIndex);
        setCurrentIndex(prev => prev - 1);
      }
    };

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

  return (
    <div style={{ height: '100vh', overflow: 'hidden' }}>
      <AnimatePresence mode="wait">
        <motion.div
          key={pageList[currentIndex].id}
          initial={{ y: currentIndex > prevIndex ? 1000 : -1000 }}
          animate={{ y: 0 }}
          exit={{ y: currentIndex < prevIndex ? 1000 : -1000 }}
          transition={{ duration: 0.8 }}
          style={{ height: '100vh', position: 'absolute', width: '100%' }}
        >
          {pageList[currentIndex].component}
        </motion.div>
      </AnimatePresence>
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 05:45:30