Mikael Lirbank
Mikael Lirbank

Reputation: 4615

Why does the enter-animation not run when mounting new page components

In this code example, the exit transition works, but the enter transition does not. I'm curious to if I approach this the right way of if this is a bug with styled-components.

The goal is to animate to 50% off screen when exiting, and to animate from 100% off screen to on-screen on enter. I've set the opacity to 50% to be able to see both page divs at all times.

I'd prefer to use Transition instead of CSSTransition as I'd like to purely use styled-components instead of mixing in regular style sheets.

Code example: https://codesandbox.io/s/sweet-mcnulty-dbptv

The animation states when exiting are (see console in CodeSandbox):

  1. entered - set initial position and disable transitions
  2. exiting - set target position and enable transitions
  3. exited - does not matter since the component is unmounted by react-router

NOTE: This (exit transition) works well.

The animation states when entering are (see console in CodeSandbox):

  1. exited - set initial position and disable transitions
  2. entering - set target position and enable transitions
  3. entered

This (the enter transition) does not animate at all. The big difference between on-exit and on-enter is that on-exit the component is already mounted, while on-enter it is mounted just before the animation should happen. My working thesis is that styled-component is not committing both the CSS updates (step 1 and 2) to the DOM individually, and thus the browser does not trigger the transition. But I Don't know this, anyhow it only happens if you mount just before the transition.

Any ideas?

Full code example (same as https://codesandbox.io/s/sweet-mcnulty-dbptv):

import React from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
import { TransitionGroup, Transition } from "react-transition-group";

const Nav = styled.nav`
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 80px;
`;

const Page = styled.div`
  background: white;
  display: flex;
  justify-content: center;
  align-items: center;
  will-change: transform;
  position: fixed;
  top: 80px;
  bottom: 0;
  left: 0;
  right: 0;
  background: ${props => (props.backgroundColor === "gray" ? "#eee" : "#efe")};
  opacity: 0.5;

  transform: ${props => {
    // Details page is green
    if (props.backgroundColor === "green") {
      // console.log(new Date().getTime(), 'Details page (css):      ', props.transitionState);
    }

    switch (props.transitionState) {
      case "entering":
        return "translateX(0)";

      case "entered":
        return "translateX(0)";

      case "exiting":
        return "translateX(-50%)";

      case "exited":
        return "translateX(100%)";

      default:
        throw new Error("This should never happen");
    }
  }};

  transition: ${props => {
    switch (props.transitionState) {
      case "entering":
        return "transform 1000ms cubic-bezier(0, 0, 0, 1)";

      case "entered":
        return "none";

      case "exiting":
        return "transform 1000ms cubic-bezier(0, 0, 0, 1)";

      case "exited":
        return "none";

      default:
        throw new Error("This should never happen");
    }
  }};
`;

const HomePage = props => {
  return (
    <Page backgroundColor="gray" transitionState={props.transitionState}>
      Home page!
    </Page>
  );
};

const DetailsPage = props => {
  console.log(new Date().getTime(), 'Details page (component):', props.transitionState);

  return (
    <Page backgroundColor="green" transitionState={props.transitionState}>
      Details page!
    </Page>
  );
};

function App() {
  return (
    <Router>
      <Nav>
        <Link to="/">Home</Link>
        <Link to="/details">Details</Link>
      </Nav>
      <Route
        render={({ location }) => {
          return (
            <TransitionGroup component={null}>
              <Transition key={location.pathname} timeout={1000}>
                {transitionState => {
                  return (
                    <Switch location={location}>
                      <Route
                        exact
                        path="/"
                        render={() => (
                          <HomePage transitionState={transitionState} />
                        )}
                      />
                      <Route
                        exact
                        path="/details"
                        render={() => (
                          <DetailsPage transitionState={transitionState} />
                        )}
                      />
                    </Switch>
                  );
                }}
              </Transition>
            </TransitionGroup>
          );
        }}
      />
    </Router>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Upvotes: 1

Views: 1247

Answers (1)

Mikael Lirbank
Mikael Lirbank

Reputation: 4615

Here is a way around it, but it requires using CSSTransition. It still does not explain why the example in the original question does not work.

https://codesandbox.io/s/page-transition-with-csstransition-working-emudw

import React from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
import { TransitionGroup, CSSTransition } from "react-transition-group";

const Nav = styled.nav`
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 80px;
`;

const Page = styled.div`
  background: white;
  display: flex;
  justify-content: center;
  align-items: center;
  will-change: transform;
  position: fixed;
  top: 80px;
  bottom: 0;
  left: 0;
  right: 0;
  background: ${props => (props.backgroundColor === "gray" ? "#eee" : "#efe")};
  opacity: 0.5;

  &.page-transition-enter {
    transform: translateX(100%);
    transition: none;
  }

  &.page-transition-enter-active {
    transform: translateX(0);
    transition: transform 1000ms cubic-bezier(0, 0, 0, 1);
  }

  &.page-transition-exit {
    transform: translateX(0);
    transition: none;
  }

  &.page-transition-exit-active {
    transform: translateX(-50%);
    transition: transform 1000ms cubic-bezier(0, 0, 0, 1);
  }
`;

const HomePage = props => {
  return <Page backgroundColor="gray">Home page!</Page>;
};

const DetailsPage = props => {
  return <Page backgroundColor="green">Details page!</Page>;
};

function App() {
  return (
    <Router>
      <Nav>
        <Link to="/">Home</Link>
        <Link to="/details">Details</Link>
      </Nav>
      <Route
        render={({ location }) => {
          return (
            <TransitionGroup component={null}>
              <CSSTransition
                key={location.pathname}
                timeout={1000}
                classNames="page-transition"
              >
                <Switch location={location}>
                  <Route exact path="/" render={() => <HomePage />} />
                  <Route exact path="/details" render={() => <DetailsPage />} />
                </Switch>
              </CSSTransition>
            </TransitionGroup>
          );
        }}
      />
    </Router>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Upvotes: 0

Related Questions