next-page-transitions
next-page-transitions copied to clipboard
Is it possible to crossfade?
All examples have one page shown at a time, is it possible to have the previous page visible while the new one transitions in? (Like the Instagram app)
@j2is I managed to do this with https://github.com/reactjs/react-transition-group instead
@mattjis Do you have an example by chance?
import React, { useRef, useState, useEffect } from 'react';
import { CSSTransition } from 'react-transition-group';
import { useRouter } from 'next/router';
import './app.css';
function PageTransition(props) {
const { c: PageComponent, lc: lastPageComponent } = props;
const componentsDiffer = lastPageComponent &&
PageComponent.key !== lastPageComponent.key;
const [transitioning, setTransitioning] = useState(componentsDiffer); // Transition if components differ
const [transitioningFor, setTransitioningFor] = useState(PageComponent.key); // Keep track of which we just transitioned in
// If the components differ and PageComponent.key is new, we need to start
// a new transition
if (componentsDiffer &&
transitioningFor !== PageComponent.key) {
setTransitioningFor(PageComponent.key);
setTransitioning(true);
}
function onFinishTransition() {
console.log('FINISHED', PageComponent.key);
setTransitioning(false);
}
function fixScroll() {
// Scroll left to 0 every frame, firefox will fuck this up if not
document.querySelector('.concerning').scrollLeft = 0;
}
console.log(PageComponent?.key, lastPageComponent?.key, transitioning, transitioningFor);
return (
<div className='concerning'>
<CSSTransition in={!transitioning} appear={true} timeout={600} classNames="page-transition" exit={false} onExited={onFinishTransition}>
<div>
{PageComponent}
</div>
</CSSTransition>
<CSSTransition in={transitioning} timeout={600} classNames="page-transition" enter={false} onExiting={fixScroll} onExited={onFinishTransition}>
<div className="outgoing">
{lastPageComponent}
</div>
</CSSTransition>
</div>
);
}
export default function MyApp(params) {
const router = useRouter();
const key = router.route;
const { Component: PageComponent, pageProps } = params;
// Needs to be in MyApp, otherwise it will get recreated with a new page when the page changes and then we lose the last component
// If you useState, react throws lots of out of order hook errors, cant store JSX inside state
const lastPageComponentRef = useRef(undefined);
const getLayoutAndPage = (PageComponent, pageProps) => {
// TODO: Convert this into just one function
// Use the layout defined at the page level, if available
const getLayout = PageComponent.getLayout || ((page) => page);
// Use the layout builder defined (which takes pageComponent and pageProps and builds the
// pageComponent too
const getLayoutBuilder = PageComponent.getLayoutBuilder || ((PageComponent, pageProps) => getLayout(<PageComponent {...pageProps} />));
return (
<div key={key}>
{getLayoutBuilder(PageComponent, pageProps)}
</div>
);
};
const c = getLayoutAndPage(PageComponent, pageProps);
const lc = lastPageComponentRef.current || getLayoutAndPage(PageComponent, pageProps);
const ret = (
<PageTransition lc={lastPageComponentRef.current} c={getLayoutAndPage(PageComponent, pageProps)} />
);
lastPageComponentRef.current = c;
return ret;
}
.concerning {
position: relative;
width: 100%;
overflow: hidden;
}
.page-transition-enter-done,
.page-transition-appear-done,
.page-transition-exit-done {
}
.page-transition-enter {
transform: translateX(-100%);
}
.page-transition-enter-active {
transform: translate(0);
transition: transform 600ms;
transition-timing-function: cubic-bezier(.26,1,.25,1);
}
.page-transition-exit {
display: block !important;
transform: translate(0);
}
.page-transition-exit-active {
display: block !important;
transform: translateX(100%);
transition: transform 600ms;
transition-timing-function: cubic-bezier(.26,1,.25,1);
}
.page-transition-exit-done {
display: none;
}
.outgoing {
position: absolute;
top: 0;
left: 0;
width: 100%;
pointer-events: none;
}
Rough code for anyone who needs it. This implements sliding pages in Next at the _app.js level. Things that I learned:
- JSX can't be stored in useState, so you have to useRef to store a previous component
- Need to have state for transitioning at top level App, otherwise it might recreate some of your state and you'll not be able to properly transition
- Firefox will fuck up horizontal scrolling even when hidden so you have to set scrollLeft on some overflow:hidden container so that your page doesnt drift
- You'll want one in transition, and one out, because you need to display 2 pages at once. One has exit disabled, the other enter disabled
- Router specific styling will have issues with this, anywhere in the codebase (styles will change as the transition occurs, causing weird disjointness)
- Getting the right variable for
in
was tough, because it needs to be true when the component changes up until the point that the transition ends. Then it needs to be flipped back so the next transition works (also why we disable exit and enter)