Fix transition fix

main
Ashelyn Dawn 3 years ago
parent d939b341a3
commit ea611b1287

@ -1,47 +1,69 @@
import Router from 'next/router'
import { useCallback, useEffect, useRef } from 'react'
type Cleanup = () => void
export default function useTransitionFix(): Cleanup{
const cleanupRef = useRef<Cleanup>(() => {})
useEffect(() => {
const changeListener = () => {
// Create a clone of every <style> and <link> that currently affects the page. It doesn't
// matter if Next.js is going to remove them or not since we are going to remove the copies
// ourselves later on when the transition finishes.
const nodes = document.querySelectorAll('link[rel=stylesheet], style:not([media=x])')
const copies = [...nodes].map((el) => el.cloneNode(true) as HTMLElement)
for (let copy of copies) {
// Remove Next.js' data attributes so the copies are not removed from the DOM in the route
// change process.
copy.removeAttribute('data-n-p')
copy.removeAttribute('data-n-href')
// Add duplicated nodes to the DOM.
document.head.appendChild(copy)
}
cleanupRef.current = () => {
for (let copy of copies) {
// Remove previous page's styles after the transition has finalized.
document.head.removeChild(copy)
}
}
}
Router.events.on('beforeHistoryChange', changeListener)
return () => {
Router.events.off('beforeHistoryChange', changeListener)
cleanupRef.current()
}
}, [])
// Return an fixed reference function that calls the internal cleanup reference.
return useCallback(() => {
cleanupRef.current()
}, [])
}
import { useEffect } from 'react';
// Temporary fix to avoid flash of unstyled content (FOUC) during route transitions.
// Keep an eye on this issue and remove this code when resolved: https://github.com/vercel/next.js/issues/17464
const useFoucFix = () => useEffect(() => {
// Gather all server-side rendered stylesheet entries.
let ssrPageStyleSheetsEntries = Array
.from(document.querySelectorAll('link[rel="stylesheet"][data-n-p]'))
.map((element) => ({
element,
href: element.getAttribute('href'),
}));
// Remove the `data-n-p` attribute to prevent Next.js from removing it early.
ssrPageStyleSheetsEntries.forEach(({ element }) => element.removeAttribute('data-n-p'));
const fixedStyleHrefs = [];
const mutationHandler = (mutations) => {
// Gather all <style data-n-href="/..."> elements.
const newStyleEntries = mutations
.filter(({ target }) => target.nodeName === 'STYLE' && target.hasAttribute('data-n-href'))
.map(({ target }) => ({
element: target,
href: target.getAttribute('data-n-href'),
}));
// Cycle through them and either:
// - Remove the `data-n-href` attribute to prevent Next.js from removing it early.
// - Remove the element if it's already present.
newStyleEntries.forEach(({ element, href }) => {
const styleExists = fixedStyleHrefs.includes(href);
if (styleExists) {
element.remove();
} else {
element.setAttribute('data-fouc-fix-n-href', href);
element.removeAttribute('data-n-href');
fixedStyleHrefs.push(href);
}
});
// Cycle through the server-side rendered stylesheets and remove the ones that
// are already present as inline <style> tags added by Next.js, so that we don't have duplicate styles.
ssrPageStyleSheetsEntries = ssrPageStyleSheetsEntries.reduce((entries, entry) => {
const { element, href } = entry;
const styleExists = fixedStyleHrefs.includes(href);
if (styleExists) {
element.remove();
} else {
entries.push(entry);
}
return entries;
}, []);
};
const observer = new MutationObserver(mutationHandler);
observer.observe(document.head, {
subtree: true,
attributeFilter: ['media'],
});
return () => observer.disconnect();
}, []);
export default useFoucFix;

@ -12,9 +12,9 @@ import '~/styles/post.css'
import RSS from '~/icons/rss.svg'
import Moon from '~/icons/moon.svg'
import { useState } from 'react'
import useLocalStorage from '~/hooks/useLocalStorage'
import useTransitionFix from '~/hooks/useStyleTransition'
import useStyleTransition from '~/hooks/useStyleTransition'
const easing = [0.175, 0.85, 0.42, 0.96]
@ -22,11 +22,11 @@ export default function App({Component, pageProps, router: {route}} : AppProps)
const [darkTheme, setDarkTheme] = useLocalStorage('ashe_darktheme', false)
const prefersReducedMotion = useReducedMotion()
const isFirstRender = useFirstRender()
const onTransitionComplete = useTransitionFix()
useStyleTransition();
const headerIsExpanded = route === '/'
const page = ({fixed}) => (
const page = () => (
<motion.div className="contentContainer" key={route} initial="initial" animate="enter" exit="exit" variants={{
initial: (prefersReducedMotion) ? {y: 0, opacity: 0} : { y : 10, opacity: 0},
enter: { y: 0, opacity: 1, transition: { duration: 0.7, ease: easing } },
@ -48,13 +48,13 @@ export default function App({Component, pageProps, router: {route}} : AppProps)
<button aria-label="Dark Mode Toggle" className="navButton" onClick={() => setDarkTheme(current => !current)}><Moon/></button>
</span>
</div>
<AnimatePresence initial={isFirstRender ? false : undefined} exitBeforeEnter onExitComplete={onTransitionComplete}>
{headerIsExpanded && page({fixed: route === '/login'})}
<AnimatePresence initial={isFirstRender ? false : undefined} exitBeforeEnter>
{headerIsExpanded && page()}
</AnimatePresence>
</div>
<div className="background">
<AnimatePresence initial={isFirstRender ? false : undefined} exitBeforeEnter onExitComplete={onTransitionComplete}>
{!headerIsExpanded && page({fixed: false})}
<AnimatePresence initial={isFirstRender ? false : undefined} exitBeforeEnter>
{!headerIsExpanded && page()}
</AnimatePresence>
</div>
</div>

Loading…
Cancel
Save