Last active
August 31, 2024 06:18
-
-
Save portedison/b9e6850d309e48051b3b8a8bd32d3b63 to your computer and use it in GitHub Desktop.
PageTransition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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