【问题标题】:page sliding animation with React Router v4 and react-transition-group v2使用 React Router v4 和 react-transition-group v2 的页面滑动动画
【发布时间】:2018-02-06 12:09:09
【问题描述】:

我正在使用 React Router v4 和 react-transition-group v2 来测试页面滑动动画。

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>
)

还有 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;
}

动画如下:

如您所见,index page 滑动到detail page 的动画是正确的(从右到左)。但是当我点击Back图标时,我希望index page从左到右出来。

我知道如果我把 CSS 改成下面这样,页面会从左到右出来:

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

但是如何将两个动画结合在一起呢?一般来说,当用户点击返回图标时,动画应该是from left to right


更新:2017.08.31

感谢@MatijaG,使用路径深度确实是一个很棒的主意。我跟着它,遇到了一个新问题。

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>
} />

并更新了 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;
}

动画:

从 '/' 到 '/games' 没问题,从 '/games' 回到 '/' 仍然没问题(类型 1:路由 A -> 路由 B,只有 2 个路由)。但是如果首先从'/'到'/games',然后从'/games'到'/games/lol',第二阶段失去动画(类型2:route A -> route B -> route C,3条或更多路线)。我们也看到从'/games/lol'回到'/games'再回到'/',幻灯片动画和type 1不一样。

有人知道这个问题吗?

【问题讨论】:

  • 请问使用 key={location.pathname.split('/')[1]} 的原因是什么?我认为这是您对 a->b->c 的问题. (/ => undefined; /games => games; /games/lol => games ) 这意味着 /games 和 /games/lol 具有相同的键,因此不会触发动画。您应该使用 key={location.key}
  • @MatijaG,是的,你是对的。这是我的错。因为HashRouter,我使用key={location.pathname.split('/')[1]}
  • @MatijaG,当我深入了解这个问题时,我发现还有其他一些问题。于是我又问了一个问题:stackoverflow.com/questions/45978263/…。如果你有时间,我真的希望我能在那里得到你的帮助。无论如何,非常感谢。

标签: react-router react-transition-group


【解决方案1】:

主要问题是,即使在您的问题中,您也没有指定应用程序应该如何知道使用哪个方向。

您可以做到这一点的一种方法是使用路径深度/长度:如果您要导航的路径比当前路径“更深”,则从右向左过渡,但如果从左向右过渡更浅?

另一个问题是路由器只给你你要去的位置,所以你可能应该保存以前的位置(或深度)以便你可以比较它。

之后只是在 css 类名之间切换的问题, 类似这样的东西:

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 示例:https://codepen.io/matijag/pen/ayXpGr?editors=0110

【讨论】:

  • 非常感谢,使用路径深度确实是一个很棒的主意。我跟着你,遇到了一个新问题。该动画仅适用于两条路线(路线 A -> 路线 B)。我对此感到困惑,我已更新问题描述以获取更多信息。
【解决方案2】:

扩展答案以容纳更多页面并保持左右转换顺序。

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

【讨论】:

    【解决方案3】:

    我在这里带来了@Darbley 答案的简化和“挂钩”版本,它工作可靠

    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%);
    }
    

    【讨论】:

      【解决方案4】:

      我为 react router v4 native 写了一个 AnimatedSwitch。

      https://javascriptrambling.blogspot.com/2020/07/react-router-native-animatedswitch.html

      我使用 history.action 来确定我是前进还是后退(即滑入或滑出)。然后确保我的过渡使用适当的动作来匹配我想要的动画。使用 history.goBack 或 history.replace 将其滑出。正常的历史记录。推或将其滑入。

            if (history.action === 'POP' || history.action === 'REPLACE') {
              setNewAnimationStyle(animationType.backNew(anim));
              setPrevAnimationStyle(animationType.backPrevious(anim));
            } else {
              setNewAnimationStyle(animationType.new(anim));
              setPrevAnimationStyle(animationType.previous(anim));
            }

      【讨论】:

        猜你喜欢
        • 2018-02-09
        • 2018-02-10
        • 2023-03-25
        • 2018-04-03
        • 2019-01-17
        • 1970-01-01
        • 2019-02-21
        • 1970-01-01
        • 2020-09-09
        相关资源
        最近更新 更多