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

Next.js中Framer Motion导航组件仅首次进入/app时触发动画的问题

解决Next.js Layout中framer-motion导航组件重复动画问题

问题核心:每次在/app下切换路由(比如从/app到/app/chats),导航组件的入场动画都会重复触发,需要仅在首次进入/app时播放一次动画。

原因

framer-motion的initial+animate组合默认会在组件每次挂载或重新渲染时触发动画。由于Next.js路由切换时,Layout组件不会卸载,但导航组件可能因路由变化导致的状态更新(比如isChatsPage的变化)重新渲染,从而再次触发动画逻辑。

解决方案:用useRef标记动画是否已执行

通过useRef记录动画是否已经播放过,动态控制framer-motion的初始状态,确保仅首次挂载时播放动画。

修改UserNavigation组件代码

import { useRef, useEffect } from 'react';
import { motion } from 'framer-motion';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline';
import classNames from 'classnames';

export const UserNavigation = () => {
  // 标记动画是否已执行过
  const hasAnimated = useRef(false);
  const pathname = usePathname();
  const isChatsPage = pathname === '/app/chats';

  // 组件挂载后标记动画已执行
  useEffect(() => {
    hasAnimated.current = true;
  }, []);

  return (
    <motion.section
      // 首次挂载时用初始偏移,之后直接用最终状态
      initial={hasAnimated.current ? { x: 0 } : { x: -56 }}
      animate={{ x: 0 }}
      transition={{ bounce: false }}
      className="h-[100%] w-14 bg-gray-900"
    >
      <div className="flex flex-col gap-4 items-center justify-center mt-2">
        <Link href="/app/chats">
          <button
            className={classNames(
              isChatsPage ? "bg-gray-700" : "hover:bg-gray-700",
              "text-white rounded-lg p-2 transition duration-200"
            )}
          >
            <ChatBubbleBottomCenterTextIcon className="w-6 h-6" />
            <span className="sr-only">Chats</span>
          </button>
        </Link>
      </div>
    </motion.section>
  );
};

原理说明

  1. hasAnimated是一个不会触发组件重新渲染的ref变量,用于记录动画是否已经执行。
  2. 组件首次挂载时,hasAnimated.current为false,initial设置为{x: -56},配合animate={x:0}触发入场动画。
  3. useEffect在组件挂载完成后将hasAnimated.current设为true,后续路由切换导致组件重新渲染时,initial直接设为最终状态{x:0},和animate状态一致,不会再次触发动画。

如果需要更精细的控制(比如离开/app再回来时也不播放动画),可以将状态持久化到localStorage:

useEffect(() => {
  const animated = localStorage.getItem('navAnimated');
  if (!animated) {
    hasAnimated.current = false;
    localStorage.setItem('navAnimated', 'true');
  } else {
    hasAnimated.current = true;
  }
}, []);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 18:50:30