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

React Router v6结合Framer Motion保留路由退出动画方案问询

React Router v6 + Framer Motion v6 路由退出动画实现方案

问题背景

使用React Router v6的createBrowserRouter(支持data loader)结合Framer Motion v6时,无法通过直接在路由element外层包裹AnimatePresence实现路由间的退出动画保留效果;改用旧版BrowserRouter虽能实现动画,但无法使用loader功能。

解决方案核心思路

将AnimatePresence移至布局组件(Layout)内部,包裹Outlet组件,同时为路由内容容器添加唯一标识key(基于当前路由路径),让Framer Motion能识别路由切换并触发动画。

具体实现步骤

1. 调整根路由结构

移除根路由element中的AnimatePresence,保持路由结构对loader的支持:

function App() {
  const router = createBrowserRouter(
    createRoutesFromElements(
      <>
        <Route
          path="/"
          element={
            <PrivateRoutes>
              <ScrollToTop>
                <Layout /> {/* 这里不再包裹AnimatePresence */}
              </ScrollToTop>
            </PrivateRoutes>
          }
        >
          {/* 子路由保持不变 */}
          <Route index element={<Dashboard />} />
          <Route
            path="/transactions"
            element={<Transactions />}
            loader={({ params }) => {
              console.log("PARAMS:", params);
              return { hello: "world" };
            }}
          />
          {/* 其他子路由... */}
        </Route>
        <Route path="*" element={<NotFound />} />
        <Route path="/auth" element={<PublicRoutes />}>
          <Route path="login" element={<Login />} />
          <Route path="reset-password" element={<ResetPassword />} />
        </Route>
      </>
    )
  );

  return <RouterProvider router={router} />;
}

2. 修改Layout组件,添加AnimatePresence包裹Outlet

在Layout中引入useLocation获取当前路由路径,作为动画容器的key,确保路由切换时触发Framer Motion的动画逻辑:

import { Outlet, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';

function Layout() {
  const location = useLocation();

  return (
    <div className="your-layout-wrapper">
      {/* 这里放置你的布局固定元素:侧边栏、顶部导航等 */}
      <div className="content-area">
        <AnimatePresence mode="wait">
          {/* 用location.pathname作为唯一key,确保路由变化时触发动画 */}
          <motion.div
            key={location.pathname}
            initial={{ opacity: 0, x: 20 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: -20 }}
            transition={{ duration: 0.3 }}
            className="route-content-container"
          >
            <Outlet /> {/* 渲染当前路由的内容 */}
          </motion.div>
        </AnimatePresence>
      </div>
    </div>
  );
}

3. 自定义路由组件的动画(可选)

如果需要为特定路由设置差异化动画,可以在对应组件外层包裹motion.div,覆盖默认动画:

// Dashboard组件示例
import { motion } from 'framer-motion';

function Dashboard() {
  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.95 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.95 }}
      transition={{ duration: 0.4 }}
    >
      {/* 组件内容 */}
    </motion.div>
  );
}

关键注意点

  • AnimatePresence必须直接包裹会被卸载/挂载的元素,这里Outlet渲染的路由内容正好符合这个要求
  • mode="wait"属性确保退出动画完全执行后,再卸载旧路由组件、挂载新组件,避免动画被中断
  • 若存在多层嵌套路由,可在对应层级的布局组件中重复此逻辑,实现嵌套路由的层级动画效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 09:58:10