ui: 页面切换过渡动画 — AnimatePresence 淡入滑出效果
This commit is contained in:
+2
-1
@@ -3,6 +3,7 @@ import { Geist } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import GlobalUserBadge from "@/components/GlobalUserBadge";
|
import GlobalUserBadge from "@/components/GlobalUserBadge";
|
||||||
import ServiceWorkerRegistrar from "@/components/ServiceWorkerRegistrar";
|
import ServiceWorkerRegistrar from "@/components/ServiceWorkerRegistrar";
|
||||||
|
import PageTransition from "@/components/PageTransition";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -40,7 +41,7 @@ export default function RootLayout({
|
|||||||
<body className={`${geistSans.variable} font-sans antialiased`}>
|
<body className={`${geistSans.variable} font-sans antialiased`}>
|
||||||
<ServiceWorkerRegistrar />
|
<ServiceWorkerRegistrar />
|
||||||
<GlobalUserBadge />
|
<GlobalUserBadge />
|
||||||
{children}
|
<PageTransition>{children}</PageTransition>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useContext, useRef, type PropsWithChildren } from "react";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { LayoutRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preserves the previous route's React context during the exit animation
|
||||||
|
* so the old page doesn't break while fading out.
|
||||||
|
*/
|
||||||
|
function FrozenRoute({ children }: PropsWithChildren) {
|
||||||
|
const ctx = useContext(LayoutRouterContext);
|
||||||
|
const frozen = useRef(ctx).current;
|
||||||
|
return (
|
||||||
|
<LayoutRouterContext.Provider value={frozen}>
|
||||||
|
{children}
|
||||||
|
</LayoutRouterContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
enter: { opacity: 0, y: 12 },
|
||||||
|
center: { opacity: 1, y: 0 },
|
||||||
|
exit: { opacity: 0, y: -12 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PageTransition({ children }: PropsWithChildren) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence mode="wait" initial={false}>
|
||||||
|
<motion.div
|
||||||
|
key={pathname}
|
||||||
|
variants={variants}
|
||||||
|
initial="enter"
|
||||||
|
animate="center"
|
||||||
|
exit="exit"
|
||||||
|
transition={{ duration: 0.2, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
|
>
|
||||||
|
<FrozenRoute>{children}</FrozenRoute>
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user