illinois / next-page-transitions

Simple and customizable page transitions for Next.js apps
MIT License
548 stars 44 forks source link

Is it possible to crossfade? #53

Open j2is opened 4 years ago

j2is commented 4 years ago

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)

mattjis commented 4 years ago

@j2is I managed to do this with https://github.com/reactjs/react-transition-group instead

Cobertos commented 3 years ago

@mattjis Do you have an example by chance?

Cobertos commented 3 years ago
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: