Skip to content

Instantly share code, notes, and snippets.

@portedison
Last active August 31, 2024 06:18
Show Gist options
  • Save portedison/b9e6850d309e48051b3b8a8bd32d3b63 to your computer and use it in GitHub Desktop.
Save portedison/b9e6850d309e48051b3b8a8bd32d3b63 to your computer and use it in GitHub Desktop.
PageTransition
// This is an example of a 'curtain' page transition for nextjs app router
// when hijacking the <Link /> to set valio's pageTransitionState.requested = href;
// I start off a transition process, switching out the the page when the curtain is closed
// I quite like the way each pass resolves the value from mapCurtain01Props, feels tidy.
'use client';
import { animated, easings, useSpring } from '@react-spring/web';
import { ReactNode, useEffect, useRef } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import { useSnapshot } from 'valtio';
import pageTransitionState from './state';
import mainHeaderState from '@/components/mainHeader/state';
import { DEBUG, GRID_COUNT } from '@/const';
import s from './PageTransition.module.scss';
import clsx from 'clsx';
interface PageTransitionProps {
children?: ReactNode | ReactNode[];
}
const debug = DEBUG && false;
const PageTransition: React.FC<PageTransitionProps> = ({ children }) => {
const devModePreventFirstRender = useRef(true);
const snap = useSnapshot(pageTransitionState);
const router = useRouter();
const pathname = usePathname();
const mapCurtain01Props = {
ready: (requested?: string) => {
debug && console.log('ready', snap.requested, snap.stage, pathname);
return { y: '100%', immediate: true };
},
exit: (requested?: string) => {
return {
to: { y: '0%' },
config: { duration: 300, easing: easings.easeInCubic },
onResolve: () => {
pageTransitionState.stage = 'exit02';
debug && console.log('exit complete', snap.requested, snap.stage, pathname);
},
};
},
exit02: (requested?: string) => {
return { y: '0%', immediate: true };
},
switch: (requested?: string) => {
return { y: '0%', immediate: true };
},
enter: (requested?: string) => {
return { y: '0%', immediate: true };
},
enter02: (requested?: string) => {
return {
to: { y: '-100%' },
config: { duration: 300, easing: easings.easeInCubic },
onResolve: () => {
pageTransitionState.stage = 'cleanup';
debug && console.log('enter02 complete', snap.requested, snap.stage, pathname);
},
};
},
cleanup: (requested?: string) => {
return {
y: '-100%',
onResolve: () => {
pageTransitionState.requested = undefined;
pageTransitionState.stage = 'ready';
debug && console.log('cleanup complete', snap.requested, snap.stage, pathname);
},
};
},
};
const mapCurtain02Props = {
ready: (requested?: string) => {
debug && console.log('ready', snap.requested, snap.stage, pathname);
return { y: '100%', immediate: true };
},
exit: (requested?: string) => {
return { y: '100%', immediate: true };
},
exit02: (requested?: string) => {
return {
to: { y: '0%' },
config: { duration: 300, easing: easings.easeInCubic },
delay: 150,
onResolve: () => {
pageTransitionState.stage = 'switch';
requested && router.push(requested);
debug && console.log('exit02 complete', snap.requested, snap.stage, pathname);
},
};
},
switch: (requested?: string) => {
return { y: '0%', immediate: true };
},
enter: (requested?: string) => {
return {
to: { y: '-100%' },
config: { duration: 300, easing: easings.easeInCubic },
delay: 500, // this gives a momne to load images
onResolve: () => {
pageTransitionState.stage = 'enter02';
debug && console.log('enter02 complete', snap.requested, snap.stage, pathname);
},
};
},
enter02: (requested?: string) => {
return { y: '-100%', immediate: true };
},
cleanup: (requested?: string) => {
return {
y: '-100%',
onResolve: () => {
pageTransitionState.requested = undefined;
pageTransitionState.stage = 'ready';
debug && console.log('cleanup complete', snap.requested, snap.stage, pathname);
},
};
},
};
const curtain01Props = useSpring(mapCurtain01Props[snap.stage](snap.requested));
const curtain02Props = useSpring(mapCurtain02Props[snap.stage](snap.requested));
useEffect(() => {
pageTransitionState.stage = 'enter';
}, []);
useEffect(() => {
if (
pageTransitionState.requested &&
pageTransitionState.stage === 'ready' &&
pageTransitionState.requested !== pathname
) {
pageTransitionState.stage = 'exit';
} else {
// something went a miss, so reset
pageTransitionState.requested = undefined;
}
}, [pageTransitionState.requested]);
useEffect(() => {
debug && console.log('pathname change', snap.requested, snap.stage, pathname);
if (DEBUG && devModePreventFirstRender.current) {
debug && console.log('skip first render', snap.requested, snap.stage, pathname);
devModePreventFirstRender.current = false;
return;
}
if (pageTransitionState.stage === 'switch') {
debug && console.log('trigger enter', snap.requested, snap.stage, pathname);
mainHeaderState.open = false;
pageTransitionState.stage = 'enter';
}
() => {};
}, [pathname]);
return (
<div>
<animated.div
style={{ ...curtain01Props }}
className={clsx(s.curtain01, s[pageTransitionState.stage])}
/>
<animated.div
style={{ ...curtain02Props }}
className={clsx(s.curtain02, s[pageTransitionState.stage])}
/>
<div className={s.children}>{children}</div>
</div>
);
};
export default PageTransition;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment