mekuri is React package for page transition animations with wait and sync modes, supporting popstate and scroll restoration.
Customize animations per component with the useMekuri hook and use with libraries like GSAP. It integrates with Next.js and Remix; can also be integrated with Next.js App Router, but for stable operation it is recommended to use Pages Router rather than App Router. Next.js Pages Router demo
$ npm i @funtech-inc/mekuriwaitandsyncmodesscrollRestorationin popstate.- When in
syncmode, routing is possible inwaitmode when in popstate. - Supports frameworks such as
Next.jsandRemix. Can also integrate withNext.js App Router. useMekurihook for each component.- Integration into smooth scrolling libraries such as lenis is also possible.
export default function App({ Component, pageProps }: AppProps) {
const { pathname } = useRouter();
return (
<MekuriContext trigger={pathname}>
<SomeAnimationComponent>
<Mekuri>
<Component
key={`${pathname + performance.now()}`}
{...pageProps}
/>
</Mekuri>
</SomeAnimationComponent>
</MekuriContext>
);
}The context to wrap the whole thing in. Set the trigger to a state to switch content. You can use pathname if you want to use it as a page transition.
Unmounting of children can be delayed by wrapping them in a Mekuri component.
Hooks that can be called within MekuriContext. Callbacks include onOnce, onLeave, onEnter, onAfterSyncEnter, onEveryLeave and onEveryEnter.
const SomeAnimationComponent = ({
children,
}: {
children: React.ReactNode;
}) => {
const ref = useRef<HTMLDivElement>(null);
useMekuri({
onLeave: (props: MekuriCallbackProps) => {
gsap.to(ref.current, {
opacity: 0,
});
},
onEnter: (props: MekuriCallbackProps) => {
gsap.to(ref.current, {
opacity: 1,
});
},
});
return <div ref={ref}>{children}</div>;
};Each callback has MekuriCallbackProps as an argument.
type MekuriCallbackProps = {
prevTrigger: Trigger | null | undefined;
currentTrigger: Trigger | null | undefined;
nextTrigger: Trigger | null | undefined;
/** Returns the Y position before leaving the page */
yPosBeforeLeave: number;
/** If # is attached to the URL when transitioning, the distance to that ID is returned. */
getHashPos: ReturnHashPosReturn;
/** intersectionObserver (
targetRef: React.RefObject<HTMLElement>,
callback: (isIntersecting: boolean) => void
) => void
* */
intersectionObserver: HandleIntersectionObserver;
/** mekuri renders based on timeout. Therefore, there are cases where the next component is rendered before the chunked Stylesheet updated by Next.js is loaded. `onStylesheetLoad` ensures that functions are executed after the Stylesheet is loaded. `onStylesheetLoad` ensures that the function is executed after the Stylesheet is loaded */
onStylesheetLoad: (callback: () => void) => void;
/** Whether the transition is by popstate */
isPopstate: boolean;
};It is possible to receive the duration set in the MekuriContext.
const { millisecond, second } = useMekuriDuration();phase : enter | leave , Specify the phase to subscribe to trigger updates
const trigger = useMekuriTrigger(phase);Since the key cannot be obtained by changing children during app router page transition, it is necessary to import LayoutRouterContext from next and pass the context to MekuriFreezer.
For more information on App Router page transition animations, see the following issue.
See this issue
"use client";
import { MekuriFreezer, Mekuri } from "@/packages/mekuri/src";
// import { LayoutRouterContext } from "next/dist/shared/lib/app-router-context";
// Next.js ^13.5.2
import { LayoutRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
import { usePathname } from "next/navigation";
export const PageTransitionLayout = ({
children,
}: {
children: React.ReactNode;
}) => {
const pathname = usePathname();
return (
<Mekuri>
<MekuriFreezer
key={`${pathname + performance.now()}`}
routerContext={LayoutRouterContext}>
{children}
</MekuriFreezer>
</Mekuri>
);
};export default function App() {
const location = useLocation();
const outlet = useOutlet();
return (
<html lang="en" className="h-full">
<head></head>
<body className="h-full">
<MekuriContext trigger={location.pathname}>
<SomeAnimationComponent>
<Mekuri>
<div key={location.pathname}>{outlet}</div>
</Mekuri>
</SomeAnimationComponent>
</MekuriContext>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}

