Skip to content
This repository has been archived by the owner on Apr 20, 2023. It is now read-only.

Is it possible to crossfade? #53

Open
j2is opened this issue Feb 29, 2020 · 3 comments
Open

Is it possible to crossfade? #53

j2is opened this issue Feb 29, 2020 · 3 comments

Comments

@j2is
Copy link

j2is commented Feb 29, 2020

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
Copy link

mattjis commented Nov 9, 2020

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

@Cobertos
Copy link

Cobertos commented Sep 6, 2021

@mattjis Do you have an example by chance?

@Cobertos
Copy link

Cobertos commented Sep 6, 2021

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)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants