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?
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
I'm bringing here simplified and "hooked" version of @Darbley answer which works reliable
App.js
import React, { useEffect, useState } from 'react'
import styled from 'styled-components/macro'
import { hot } from 'react-hot-loader/root'
import { setConfig } from 'react-hot-loader'
import { Switch, Route } from 'react-router-dom'
import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { compose } from 'redux'
import { withRouter } from '@hoc'
import Home from '@components/Home/Home'
import About from './About/About'
import Contact from './Contact/Contact'
import Navbar from './Navbar/Navbar'
setConfig({
reloadHooks: false
})
const AppContainer = styled.main``
const pages = [
{ path: '/', order: 1 },
{ path: '/about', order: 2 },
{ path: '/contact', order: 3 }
]
const App = ({ location }) => {
const [pageDirection, setPageDirection] = useState()
const [currentPath, setCurrentPath] = useState(location.pathname)
const [currentPathOrder, setCurrentPathOrder] = useState(
pages.filter(({ path }) => path === location.pathname)[0].order
)
const currentKey = location.pathname.split('/')[1] || '/'
useEffect(() => {
const newPath = location.pathname
const newPathOrder = pages.filter(({ path }) => path === newPath)[0].order
if (newPath !== currentPath) {
const direction = currentPathOrder < newPathOrder ? 'left' : 'right'
setCurrentPath(newPath)
setCurrentPathOrder(newPathOrder)
setPageDirection(direction)
}
})
return (
<AppContainer>
<Navbar />
<TransitionGroup className={`${pageDirection}`}>
<CSSTransition key={currentKey} timeout={1000} classNames={'route'}>
<div className="route__container">
<Switch location={location}>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</div>
</CSSTransition>
</TransitionGroup>
</AppContainer>
)
}
export default process.env.NODE_ENV === 'development'
? compose(withRouter)(hot(App))
: compose(withRouter)(App)
index.scss
.route__container {
width: 100%;
height: 100vh;
position: absolute;
top: 0px;
left: 0px;
transition: 1s;
}
.left .route-enter {
transform: translateX(100%);
}
.left .route-enter-active {
transform: translateX(0%);
}
.left .route-exit {
transform: translateX(-100%);
}
.left .route-exit-active {
transform: translateX(-100%);
}
.right .route-enter {
transform: translateX(-100%);
}
.right .route-enter-active {
transform: translateX(0%);
}
.right .route-exit {
transform: translateX(100%);
}
.right .route-exit-active {
transform: translateX(100%);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With