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/mekuri
wait
andsync
modesscrollRestoration
in popstate.- When in
sync
mode, routing is possible inwait
mode when in popstate. - Supports frameworks such as
Next.js
andRemix
. Can also integrate withNext.js App Router
. useMekuri
hook 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>
);
}