page sliding animation with React Router v4 and react-transition-group v2

吃可爱长大的小学妹 提交于 2019-12-05 00:11:49

问题


I'm using React Router v4 and react-transition-group v2 to test the page sliding animation.

const RouterMap = () => (
  <Router>
    <Route render={({ location }) =>
      <TransitionGroup>
        <CSSTransition key={location.pathname.split('/')[1]} timeout={500} classNames="pageSlider" mountOnEnter={true} unmountOnExit={true}>
          <Switch location={location}>
            <Route path="/" exact component={ Index } />
            <Route path="/comments" component={ Comments } />
            <Route path="/opinions" component={ Opinions } />
            <Route path="/games" component={ Games } />
          </Switch>
        </CSSTransition>
      </TransitionGroup>
    } />
  </Router>
)

And the CSS:

.pageSlider-enter {
  transform: translate3d(100%, 0, 0);
}

.pageSlider-enter.pageSlider-enter-active {
  transform: translate3d(0, 0, 0);
  transition: all 600ms;
}
.pageSlider-exit {
  transform: translate3d(0, 0, 0);
}

.pageSlider-exit.pageSlider-exit-active {
  transform: translate3d(-100%, 0, 0);
  transition: all 600ms;
}

The animation is as bellow:

As you see, the animation that index page slide to the detail page is all right(right to left). But when I click the Back icon, I hope index page comes out from left to right.

I know if I change the CSS as bellow, the page will come out from left to right:

.pageSlider-enter {
  transform: translate3d(-100%, 0, 0);
}
.pageSlider-exit.pageSlider-exit-active {
  transform: translate3d(100%, 0, 0);
  transition: all 600ms;
}

But how combine the two animations together? Generally speaking, whenever user clicks the back icon, the animation should be from left to right.


Update: 2017.08.31

Thanks for @MatijaG, using the path depth is really an awesome idea. I followed it and got a new problem.

function getPathDepth(location) {
  let pathArr = (location || {}).pathname.split('/');
  pathArr = pathArr.filter(n => n !== '');
  return pathArr.length;
}

<Route render={({ location }) =>
  <TransitionGroup>
    <CSSTransition
      key={location.pathname.split('/')[1]}
      timeout={500}
      classNames={ getPathDepth(location) - this.state.prevDepth > 0 ? 'pageSliderLeft' : 'pageSliderRight' }
      mountOnEnter={true}
      unmountOnExit={true}
    >
      <Switch location={location}>
        <Route path="/" exact component={ Index } />
        <Route path="/comments" component={ Comments } />
        <Route path="/opinions" component={ Opinions } />
        <Route path="/games/lol" component={ LOL } /> // add a new route
        <Route path="/games" component={ Games } />
      </Switch>
    </CSSTransition>
  </TransitionGroup>
} />

And updated CSS:

.pageSliderLeft-enter {
  transform: translate3d(100%, 0, 0);
}
.pageSliderLeft-enter.pageSliderLeft-enter-active {
  transform: translate3d(0, 0, 0);
  transition: all 600ms;
}
.pageSliderLeft-exit {
  transform: translate3d(0, 0, 0);
}
.pageSliderLeft-exit.pageSliderLeft-exit-active {
  transform: translate3d(100%, 0, 0);
  transition: all 600ms;
}

.pageSliderRight-enter {
  transform: translate3d(-100%, 0, 0);
}
.pageSliderRight-enter.pageSliderRight-enter-active {
  transform: translate3d(0, 0, 0);
  transition: all 600ms;
}
.pageSliderRight-exit {
  transform: translate3d(0, 0, 0);
}
.pageSliderRight-exit.pageSliderRight-exit-active {
  transform: translate3d(-100%, 0, 0);
  transition: all 600ms;
}

The animation:

From '/' to '/games' is ok, and from '/games' back to '/' is still ok(type 1: route A -> route B, only 2 routes). But if firstly from '/' to '/games', and then from '/games' to '/games/lol', the second phase lose the animation(type 2: route A -> route B -> route C, 3 or more routes). We also see that from '/games/lol' back to '/games' and then back to '/', the slide animation is not same as type 1.

Anyone has any idea about this problem?


回答1:


Well the main problem is that even in your question you did not specify how should the app know which direction to use.

One way you could do it is by using the path depth/length: if the path you are navigating to is "deeper" than the current path, transition right to left, but if its shallower transition from left right?

The other problem is that router only gives you the location you are going to, so you should probably save the previous location (or depth) so you can compare it to.

After that is just a matter of switching between css classnames, something along this lines:

import React from 'common/react'
import { withRouter, Route, Switch } from 'react-router'

function getPathDepth (location) {
    return (location || {} ).pathname.split('/').length
}
class RouterMap extends React.PureComponent {
    constructor (props, context) {
        super(props, context)
        this.state = {
            prevDepth: getPathDepth(props.location),
        }
    }

    componentWillReceiveProps () {
        this.setState({ prevDepth: getPathDepth(this.props.location) })
    }
    render () {

        return (
            <Router>
                <Route render={ ({ location }) =>
                    (<TransitionGroup>
                        <CSSTransition
                            key={ location.pathname.split('/')[1] }
                            timeout={ 500 }
                            classNames={ getPathDepth(location) - this.state.prevDepth ? 'pageSliderLeft' : 'pageSliderRight' } mountOnEnter={ true } unmountOnExit={ true }
                        >
                            <Switch location={ location }>
                                <Route path='/' exact component={ Index } />
                                <Route path='/comments' component={ Comments } />
                                <Route path='/opinions' component={ Opinions } />
                                <Route path='/games' component={ Games } />
                            </Switch>
                        </CSSTransition>
                    </TransitionGroup>)
                }
                />
            </Router>
        )
    }
}

export default withRouter(RouterMap)

Codepen example: https://codepen.io/matijag/pen/ayXpGr?editors=0110




回答2:


Expanded answer to accommodate more pages and keep left and right transitions in order.

import React from 'react';
import './assets/scss/styles.scss';
import { Route, Switch, withRouter  } from "react-router-dom";
import {TransitionGroup, CSSTransition } from 'react-transition-group';
import Header from './components/Header';
import Home from './pages/Home';
import Yellow from './pages/Yellow';
import Red from './pages/Red';
import Green from './pages/Green';
import Blue from './pages/Blue';
import Purple from './pages/Purple';
const pages = [
    { path: '/', name: 'home', order: 1 },
    { path: '/yellow', name: 'yellow', order: 2 },
    { path: '/red', name: 'red', order: 3 },
    { path: '/green', name: 'green', order: 4 },
    { path: '/blue', name: 'blue', order: 5 },
    { path: '/purple', name: 'purple', order: 6 }
]

class App extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            currentPage: this.setPage(this.props.location.pathname),
            curPageOrder: this.setCurrentOrder(this.props.location.pathname),
            newPageOrder: null
        }
    }
    componentDidUpdate(prevProps, prevState){
        console.log('Component did update');

        let newPage = this.setPage(this.props.location.pathname);
        let newPageOrder = pages.filter(function (page) {
            return page.name === newPage;
        });

        let curPage = this.state.currentPage;
        let curPageOrder = pages.filter(function (page) {
            return page.name === curPage;
        });

        if( newPage !== curPage){
            console.log('new page');
            let direction = curPageOrder[0].order < newPageOrder[0].order ? 'left' : 'right';
            // Set State
            this.setState({
                currentPage: newPage,
                pageDirection: direction,
                curPageOrder: curPageOrder[0].order,
                newPageOrder: newPageOrder[0].order,
            })

        }

    }
    setCurrentOrder = (path) => {
        let curPageOrder = pages.filter(function (page) {
            return page.path === path;
        });

        return curPageOrder[0].order;
    }

    setPage = (pathname) => {
        // SET PAGE FOR CSS CLASSES
        let page = null;
        switch (pathname){
            case('/'):
                page = 'home';
                break;
            case('/yellow'):
                page = 'yellow';
                break;
            case('/red'):
                page = 'red';
                break;
            case('/green'):
                page = 'green';
                break;
            case('/blue'):
                page = 'blue'
                break;
            case('/purple'):
                page = 'purple';
                break;
            default:
                page = 'home';

        }

        return page;
    }
    render() {
        const { location } = this.props;
        const currentKey = location.pathname.split("/")[1] || "/";


        return (
            <div className={`wrapper ${this.setPage(this.props.location.pathname)}`}>
                <Header />
                <div className={`wrap ${currentKey} `}>
                    <TransitionGroup  className={`transition-group ${this.state.pageDirection}`}>
                        <CSSTransition
                            key={currentKey}
                            timeout={{ enter: 800, exit: 400 }}
                            classNames={'transition-wrap'}

                        >

                            <section className={`route-section fade`}>
                                <Switch location={location}>
                                    <Route exact path="/" component={() => <Home />} />
                                    <Route path="/yellow" component={() => <Yellow /> } />
                                    <Route path="/red" component={() => <Red /> } />
                                    <Route path="/green" component={() => <Green /> } />
                                    <Route path="/blue" component={() => <Blue /> } />
                                    <Route path="/purple" component={() => <Purple /> } />   
                                </Switch>
                            </section>

                        </CSSTransition>
                    </TransitionGroup>
                </div>
            </div>
        )
    }
}
export default withRouter(App);

https://codesandbox.io/s/github/darbley/react-page-slide-transitions



来源:https://stackoverflow.com/questions/45932263/page-sliding-animation-with-react-router-v4-and-react-transition-group-v2