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

使用Router加载framer-motion页面时的动画异常问题

问题描述

场景

  • 基于Vite搭建React项目,使用framer-motion实现页面元素动画
  • 在src/components/FramerMotion目录下创建Motion.jsx组件,通过react-router-dom配置多页面路由,使用useNavigate()实现页面跳转

预期效果

  • 从首页(Hello.jsx)导航至Motion页面时,所有<motion.div>元素按配置的variants规则,以0.25秒的延迟依次加载动画
  • 刷新Motion页面时,所有元素同样按上述延迟规则重新触发加载动画

实际异常

  • 路由跳转时动画表现正常
  • 刷新Motion页面时,前两个<motion.div>直接显示无延迟,其余元素可正常按延迟加载

相关代码

Motion.jsx

import {
    motion,
} from "framer-motion"

const gridContainerVariants = {
    hidden: { opacity: 0 },
    show: {
        opacity: 1,
        transition: {
            staggerChildren: 0.25,
        },
    },
}

const gridSquareVariants = {
    hidden: { opacity: 0 },
    show: { opacity: 1 },
}

const Motion = () => {
    return (
        <div className="flex flex-col gap-10 overflow-x-hidden">
            <motion.div
                variants={gridContainerVariants}
                initial="hidden"
                animate="show"
                className="grid grid-cols-3 p-10 gap-10"
            >
                {/* Fade Up */}
                <motion.div
                    variants={gridSquareVariants}
                    className="bg-slate-800 aspect-square rounded-lg justify-center flex items-center gap-10"
                >
                </motion.div>
                {/* Shape Shifting */}
                <motion.div
                    variants={gridSquareVariants}
                    className="bg-slate-800 aspect-square rounded-lg justify-center flex items-center gap-10"
                >
                </motion.div>
                {/* Button */}
                <motion.div
                    variants={gridSquareVariants}
                    className="bg-slate-800 aspect-square rounded-lg justify-center flex items-center gap-10"
                >
                </motion.div>
                {/* Drag */}
                <motion.div
                    variants={gridSquareVariants}
                    className="bg-slate-800 aspect-square rounded-lg justify-center flex items-center gap-10"
                >
                </motion.div>
                {/* Scroll Progress */}
                <motion.div
                    variants={gridSquareVariants}
                    className="bg-slate-800 aspect-square rounded-lg justify-center flex items-center gap-10"
                >
                </motion.div>
                {/* SVG Animation */}
                <motion.div
                    variants={gridSquareVariants}
                    className="bg-slate-800 aspect-square rounded-lg justify-center flex items-center gap-10"
                >
                </motion.div>
            </motion.div>
        </div>
    )
}

export default Motion

App.jsx

import Motion from "./components/FramerMotion/Motion";
import Hello from "./components/HelloTest/Hello";
import Auction from "./components/HOC/Auction";
import MovingBorder from "./components/Tailwind/MovingBorder";
import ResponsiveNavigation1 from "./components/Tailwind/ResponsiveNavigation1";
import ResponsiveNavigation2 from "./components/Tailwind/ResponsiveNavigation2";
import { BrowserRouter, Routes, Route } from "react-router-dom";

function App() {
    return (
        <>
            <BrowserRouter>
                <Routes>
                    <Route path="/" element={<Hello />} />
                    <Route path="/auction" element={<Auction />} />
                    <Route path="/movingborder" element={<MovingBorder />} />
                    <Route path="/responsivenavigation1" element={<ResponsiveNavigation1 />}/>
                    <Route path="/responsivenavigation2" element={<ResponsiveNavigation2 />}/>
                    <Route path="/motion" element={<Motion />} />
                </Routes>
            </BrowserRouter>
        </>
    );
}

export default App;

项目目录结构

/public/
  └─ vite.svg
/src/
  /components/
    /FramerMotion/
      └─ Motion.jsx
    /HelloTest/
      └─ Hello.jsx
    /HOC/
      ├─ Auction.jsx
      ├─ HOC.jsx
      ├─ Person1.jsx
      └─ Person2.jsx
    /Tailwind/
      ├─ MovingBorder.jsx
      ├─ ResponsiveNavigation1.jsx
      └─ ResponsiveNavigation2.jsx
  ├─ App.jsx
  ├─ index.css
  └─ main.jsx
/.gitignore
/eslint.config.js
/index.html
/package.json
/postcss.config.js
/README.md
/tailwind.config.js
/vite.config.js

解决方案

原因分析

刷新页面时浏览器会优先渲染可视区域内的元素,导致framer-motion父容器的staggerChildren延迟规则未及时作用于前两个子元素,子元素的hidden初始状态被跳过。

可行修复方案

方案1:给父容器动画添加极小延迟

修改父容器的过渡配置,确保所有子元素的初始状态生效后再启动动画:

const gridContainerVariants = {
    hidden: { opacity: 0 },
    show: {
        opacity: 1,
        transition: {
            staggerChildren: 0.25,
            delay: 0.05 // 添加极小延迟,避免初始渲染冲突
        },
    },
}

方案2:给子元素显式声明初始状态

虽然父容器的variants会传递给子元素,但刷新时可能存在状态传递延迟,给每个子元素显式设置initial="hidden":

<motion.div
    variants={gridSquareVariants}
    initial="hidden"
    className="bg-slate-800 aspect-square rounded-lg justify-center flex items-center gap-10"
>
</motion.div>

方案3:用AnimatePresence包裹父容器

针对路由场景,AnimatePresence可以更精准地控制页面挂载时的动画触发逻辑:

import { motion, AnimatePresence } from "framer-motion"

const Motion = () => {
    return (
        <div className="flex flex-col gap-10 overflow-x-hidden">
            <AnimatePresence mode="wait">
                <motion.div
                    key="motion-grid"
                    variants={gridContainerVariants}
                    initial="hidden"
                    animate="show"
                    exit="hidden"
                    className="grid grid-cols-3 p-10 gap-10"
                >
                    {/* 子元素保持不变 */}
                </motion.div>
            </AnimatePresence>
        </div>
    )
}

mode="wait"会确保初始动画完成后再处理其他状态,避免刷新时的渲染优先级冲突。


内容的提问来源于stack exchange,提问作者A AMRITH GOLD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 16:42:00