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

手动修改URL时Framer Motion退出动画失效的排查与解决

解决React路由中AnimatePresence手动输入URL时退出动画失效的问题

问题本质

手动输入URL或刷新页面时,浏览器会完全重新加载整个应用——之前的路由组件已经被彻底销毁,AnimatePresence根本没有机会触发退出动画(毕竟旧组件都不存在了)。而用navigate()是单页应用内的客户端路由跳转,组件是在React生命周期内正常卸载,所以AnimatePresence能捕获到卸载事件并执行退出动画。

解决方案

方案1:通过本地存储记录历史路由,模拟退出动画

核心思路是页面初始化时,先渲染上一次访问的路由组件并执行退出动画,完成后再切换到当前路由组件。

import { useState, useEffect } from 'react';
import { useLocation, Routes, Route } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import Login from './Login';
import Register from './Register';
import NotFound from './NotFound';

// 维护路由与组件的映射,避免重复条件判断
const ROUTE_COMPONENTS = {
  '/login': <Login />,
  '/register': <Register />,
  '*': <NotFound />
};

function App() {
  const location = useLocation();
  const [showPrevRoute, setShowPrevRoute] = useState(true);
  const prevRoute = localStorage.getItem('prevRoute');

  // 初始化时判断是否有历史路由,没有则直接显示当前路由
  useEffect(() => {
    if (!prevRoute) setShowPrevRoute(false);
  }, [prevRoute]);

  // 每次路由跳转时,记录当前路径到本地存储
  useEffect(() => {
    localStorage.setItem('prevRoute', location.pathname);
  }, [location.pathname]);

  // 历史路由组件退出动画完成后,切换到当前路由
  const handleExitComplete = () => {
    setShowPrevRoute(false);
  };

  return (
    <AnimatePresence mode="wait">
      {/* 渲染历史路由组件并执行退出动画 */}
      {showPrevRoute && prevRoute && (
        <motion.div
          key={prevRoute}
          initial={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: -100 }}
          transition={{ duration: 0.3 }}
          onExitComplete={handleExitComplete}
        >
          {ROUTE_COMPONENTS[prevRoute] || ROUTE_COMPONENTS['*']}
        </motion.div>
      )}
      {/* 渲染当前路由组件并执行进入动画 */}
      {!showPrevRoute && (
        <motion.div
          key={location.pathname}
          initial={{ opacity: 0, x: 100 }}
          animate={{ opacity: 1, x: 0 }}
          transition={{ duration: 0.3 }}
        >
          <Routes>
            <Route path="/login" element={<Login />} />
            <Route path="/register" element={<Register />} />
            <Route path="*" element={<NotFound />} />
          </Routes>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

export default App;

方案2:添加初始化过渡层(简化版)

如果觉得方案1太繁琐,可以在应用初始化时显示一个全屏过渡层,覆盖页面,等当前路由组件渲染完成后再隐藏,模拟衔接效果,让手动输入URL的跳转体验不突兀。

import { useState, useEffect } from 'react';
import { Routes, Route } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import Login from './Login';
import Register from './Register';
import NotFound from './NotFound';

function App() {
  const [isInitialLoad, setIsInitialLoad] = useState(true);

  useEffect(() => {
    // 延迟隐藏过渡层,与动画时长匹配
    const timer = setTimeout(() => {
      setIsInitialLoad(false);
    }, 300);
    return () => clearTimeout(timer);
  }, []);

  return (
    <>
      {/* 初始化过渡层 */}
      {isInitialLoad && (
        <motion.div
          className="fixed inset-0 bg-white z-50 flex items-center justify-center"
          initial={{ opacity: 1 }}
          animate={{ opacity: 0 }}
          transition={{ duration: 0.3 }}
          onAnimationComplete={() => setIsInitialLoad(false)}
        >
          {/* 可以添加加载图标或文字 */}
          <div>加载中...</div>
        </motion.div>
      )}
      {/* 原有路由动画逻辑 */}
      <AnimatePresence mode="wait">
        <Routes location={location} key={location.pathname}>
          <Route path="/login" element={<Login />} />
          <Route path="/register" element={<Register />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </AnimatePresence>
    </>
  );
}

export default App;

注意事项

  • 方案1中,可在用户关闭浏览器时清除本地存储的历史路由(比如监听beforeunload事件),避免下次打开应用时显示旧路由。
  • 两种方案的动画时长要保持一致,保证体验连贯。
  • 方案1的路由映射对象需要同步更新,避免新增路由后无法匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 17:55:15