NextJS 14 Framer Motion 上的退出动画

1 next.js framer-motion

我正在尝试使用 Framer Motion 将页面过渡动画添加到 Next js 14 应用程序。我有 PageTransitionLayout.tsx 看起来像这样

'use client';

import { motion, AnimatePresence } from "framer-motion";
import { ReactNode, FC } from "react";

import { usePathname } from "next/navigation";

interface ILayoutProps {
  children: ReactNode;
}

const PageTransitionLayout: FC<ILayoutProps> = ({ children }) => {
  const pathname = usePathname()

  return (
      <AnimatePresence mode={'wait'}>
        <motion.div 
         key={`${pathname}1`}
         className="absolute top-0 left-0 w-full h-screen bg-green-400 origin-middle"
         initial={{ scaleY: 1 }}
         animate={{ scaleY: 0.5 }}
         exit={{ scaleY: 0}}
         transition={{ duration: 1, ease: [0.22, 1, 0.36, 1] }}
       />
       {children}
      </AnimatePresence>
  );
}

export default PageTransitionLayout;
Run Code Online (Sandbox Code Playgroud)

并像这样在 app/contact/pages.tsx 中使用它

"use client";

import PageTransitionLayout from "../ui/PageTransitionLayout";

export default function Contact() {
    return (
    <PageTransitionLayout>
      <div className="grid h-[90vh] place-items-center bg-orange-400">
        <h1 className="font-bold text-4xl">Contact</h1>
      </div>
    </PageTransitionLayout>
    )
}
Run Code Online (Sandbox Code Playgroud)

但是当导航到不同的页面时,运动 div 的退出动画不会触发。可能是什么问题?

小智 6

在 NextJS 13/14 中,您必须将页面包装在 HOC 中以减慢应用程序路由器的速度。下面提出的解决方案将引入更多需要解决的问题,例如:

  • 使用loading.js应用程序目录中的文件的悬念边界将无法正确加载到子级中,从而使您无法使用loading.js
    • 该解决方案将导致页面重新渲染更多,但将有效地提供您正在寻找的退出过渡。
  • 页面加载后将被“冻结”,这会给当前状态下的应用程序路由器带来压力。

layout.js

/src/app/layout.js||/app/layout.js

import { Inter } from 'next/font/google'
import './globals.css'
import PageAnimatePresence from '@components/HOC/PageAnimatePresence'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Your Website Title',
  description: 'Website metadata description.',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body
        className={inter.className + ` bg-blue-500 transition-colors duration-1000`}
        id="page-container"
      >
        <PageAnimatePresence>{children}</PageAnimatePresence>
      </body>
    </html>
  )
}
Run Code Online (Sandbox Code Playgroud)

PageAnimatePresence.js

/src/app/components/HOC/PageAnimatePresence.js||/app/components/HOC/PageAnimatePresence.js

'use client'

import { usePathname } from 'next/navigation'
import { AnimatePresence, motion } from 'framer-motion'
import FrozenRoute from './FrozenRoute'

const PageAnimatePresence = ({ children }) => {
  const pathname = usePathname()

  return (
    <AnimatePresence mode="wait">
      {/**
       * We use `motion.div` as the first child of `<AnimatePresence />` Component so we can specify page animations at the page level.
       * The `motion.div` Component gets re-evaluated when the `key` prop updates, triggering the animation's lifecycles.
       * During this re-evaluation, the `<FrozenRoute />` Component also gets updated with the new route components.
       */}
      <motion.div key={pathname}>
        <FrozenRoute>{children}</FrozenRoute>
      </motion.div>
    </AnimatePresence>
  )
}

export default PageAnimatePresence
Run Code Online (Sandbox Code Playgroud)

FrozenRoute.js

/src/app/components/HOC/FrozenRoute.js||/app/components/HOC/FrozenRoute.js

'use client'

import { useContext, useRef } from 'react'
import { LayoutRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'

const FrozenRoute = ({ children }) => {
  const context = useContext(LayoutRouterContext)
  const frozen = useRef(context).current

  return <LayoutRouterContext.Provider value={frozen}>{children}</LayoutRouterContext.Provider>
}

export default FrozenRoute
Run Code Online (Sandbox Code Playgroud)

template.js

/src/app/template.js||/app/template.js

'use client'
import { motion } from 'framer-motion'

const variants = {
  hidden: { opacity: 0, x: 0, y: 0 },
  enter: { opacity: 1, x: 0, y: 0 },
}

export default function Template({ children }) {
  return (
    <motion.main
      variants={variants}
      initial="hidden"
      exit="hidden"
      animate="enter"
      transition={{ type: 'linear', duration: 0.25 }}
      key="LandingPage"
    >
      {children}
    </motion.main>
  )
}
Run Code Online (Sandbox Code Playgroud)