Created
August 27, 2024 15:07
-
-
Save iDVB/4f6ba81d4cdf37dd16fefdfca0fd5777 to your computer and use it in GitHub Desktop.
Custom NextJS ScrollRestoration with async triggerRestore
This file contains 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
import * as React from 'react' | |
import { ScrollRestorationProvider } from './ScrollRestoration' | |
const ignorePattern = /^\/ai(\/.*)?$/ | |
function MyApp(props) { | |
const { Component, pageProps, router } = props | |
return ( | |
<ScrollRestorationProvider router={router} ignorePattern={ignorePattern}> | |
<Component {...pageProps} /> | |
</ScrollRestorationProvider> | |
) | |
} | |
export default MyApp |
This file contains 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
// You can then optionally use 'ignorePattern' with ScrollRestorationProvider do turn off all scroll restoration | |
// for specific routes. You can then use useScrollRestorationContext to trigger "when" to restore scroll. This supports | |
// the scenerio where you want to setup Page Transitions where you want to only trigger the restore after/during the animation. | |
const { triggerRestore } = useScrollRestorationContext() | |
... | |
triggerRestore(route, false) |
This file contains 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
import React, { createContext, useContext, useEffect, useRef } from 'react' | |
import Head from 'next/head' | |
import Router from 'next/router' | |
const ScrollRestorationContext = createContext(null) | |
export const useScrollRestoration = ({ router, ignorePattern: optsIgnorePattern }) => { | |
const shouldScrollRestore = useRef(true) | |
const triggerRestore = React.useCallback( | |
(url, ignorePattern = optsIgnorePattern) => { | |
console.log({ scrollRestoration: window.history.scrollRestoration }) | |
if (!ignorePattern?.test?.(url)) { | |
if (shouldScrollRestore.current) { | |
console.log('RESTORING') | |
setTimeout(() => { | |
shouldScrollRestore.current = false | |
restoreScrollPos(url) | |
}, 1) | |
} else { | |
console.log('SCROLLING TO TOP') | |
window.scrollTo(0, 0) | |
} | |
} | |
}, | |
[optsIgnorePattern], | |
) | |
const restoreScrollPos = (url) => { | |
const json = sessionStorage.getItem(`scrollPos:${url}`) | |
const scrollPos = json ? JSON.parse(json) : undefined | |
if (scrollPos) { | |
window.scrollTo(scrollPos.x, scrollPos.y) | |
} | |
} | |
useEffect(() => { | |
if (typeof window === undefined || !('scrollRestoration' in window.history)) { | |
return | |
} | |
window.history.scrollRestoration = 'manual' | |
const saveScrollPos = (url) => { | |
sessionStorage.setItem( | |
`scrollPos:${url}`, | |
JSON.stringify({ x: window.scrollX, y: window.scrollY }), | |
) | |
} | |
const onBeforeUnload = (event) => { | |
saveScrollPos(router.asPath) | |
delete event['returnValue'] | |
} | |
const onRouteChangeStart = () => { | |
saveScrollPos(router.asPath) | |
} | |
window.addEventListener('beforeunload', onBeforeUnload) | |
Router.events.on('routeChangeStart', onRouteChangeStart) | |
Router.beforePopState((state) => { | |
console.log('beforePopState') | |
state.options.scroll = false | |
shouldScrollRestore.current = true | |
return true | |
}) | |
if (shouldScrollRestore.current) { | |
triggerRestore(router.asPath) | |
} | |
return () => { | |
window.removeEventListener('beforeunload', onBeforeUnload) | |
Router.events.off('routeChangeStart', onRouteChangeStart) | |
Router.beforePopState(() => true) | |
} | |
}, [router, triggerRestore]) | |
return { triggerRestore } | |
} | |
export const useScrollRestorationContext = () => useContext(ScrollRestorationContext) | |
export const ScrollRestorationProvider = ({ children, router, ignorePattern }) => { | |
const { triggerRestore } = useScrollRestoration({ router, ignorePattern }) | |
return ( | |
<> | |
<Head> | |
<script | |
dangerouslySetInnerHTML={{ | |
__html: ` | |
window.history.scrollRestoration = 'manual' | |
`, | |
}} | |
/> | |
</Head> | |
<ScrollRestorationContext.Provider value={{ triggerRestore }}> | |
{children} | |
</ScrollRestorationContext.Provider> | |
</> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment