FIXED - Move page transition from template to persistent layout so it animates
CI / quality (push) Canceled after 0s
CI / quality (push) Canceled after 0s
A template.tsx is re-created on every navigation, resetting AnimatePresence so neither enter nor exit ran. Move the transition into a PageTransition client component rendered in the persistent (site) layout, so AnimatePresence survives navigations and the old page animates out before the new one in.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { LayoutRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useContext, useState, type ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* Pins the layout-router context captured when this instance mounted. Each
|
||||
* keyed page gets its own FrozenRouter, so the *outgoing* page keeps rendering
|
||||
* its old route data while it animates out.
|
||||
*/
|
||||
function FrozenRouter({ children }: { children: ReactNode }) {
|
||||
const context = useContext(LayoutRouterContext);
|
||||
const [frozen] = useState(context);
|
||||
|
||||
if (!frozen) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return <LayoutRouterContext.Provider value={frozen}>{children}</LayoutRouterContext.Provider>;
|
||||
}
|
||||
|
||||
/**
|
||||
* One unified page transition on every navigation. Lives in the (persistent)
|
||||
* site layout — NOT in a `template.tsx`, which Next re-creates on each
|
||||
* navigation and would reset AnimatePresence so nothing animates. The old page
|
||||
* animates out (rise + blur), then the new page animates in from below.
|
||||
*/
|
||||
export function PageTransition({ children }: { children: ReactNode }) {
|
||||
const reducedMotion = useReducedMotion();
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={pathname}
|
||||
initial={reducedMotion ? false : { opacity: 0, y: 20, filter: "blur(10px)" }}
|
||||
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
||||
exit={reducedMotion ? { opacity: 0 } : { opacity: 0, y: -20, filter: "blur(10px)" }}
|
||||
transition={reducedMotion ? { duration: 0 } : { duration: 0.45, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
<FrozenRouter>{children}</FrozenRouter>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user