写在前面
之前我写过关于react-transition-group和react-motion的使用教程,就控制粒度上来说,react-motion要好很多,但是react-motion有个比较麻烦的问题,就是我暂时没找到开箱即用的动画封装,而用react-motion也没法使用animate.css,所以如果项目仅仅只用于web端,没考虑native,那么建议还是使用react-transition-group,会方便很多
用到的第三方库
- react-router-dom 4.2.2 用于路由
- react-transition-group 2.2.1 用于react动画实现,这里需要注意下,使用的不是版本1,而是只包含{Transition, TransitionGroup, CSSTransition}的版本2
- animate.css 用于动画效果
热身
在正式开始写路由的切换动画前,我们先用react-transition-group
结合animate.css
来实现一个简单的组件进出场动画,以此回顾之前关于react-transition-group
的知识
react-transition-group文档
<div>
<button onClick={this.toggleState}>click</button>
{/*第一个例子*/}
<CSSTransition
in={this.state.show}
classNames={{
enter: 'animated',
enterActive: 'bounceIn',
exit: 'animated',
exitActive: 'bounceOut'
}}
timeout={500}
mountOnEnter={true}
unmountOnExit={true}
>
<div className="box" />
</CSSTransition>
</div>
效果
代码很简单,用
in
控制组件的显示和隐藏,用classNames
控制组件进出场的className,稍微需要注意的是与animate.css
的结合方式
路由切换
回顾了组件的进场和出场动画实现后,我们正式来开始写路由的切换动画。先理清楚思路,在react-router4
里面,每个路由对应其实就是一个组件,无非就是在路由匹配到的时候,将CSSTransition
的in
设置为true
,不匹配设置为false
,仅此而已。
唯一麻烦的地方在于怎么获取路由的匹配信息,翻看react-router4
的api,我们看到,Route
和渲染有关的props有三个,component
,render
,children
,component
和render
都拿不到匹配信息,只要路由匹配到,组件就会mount,反之,就会unmount,我们无法进行控制,而children
正好符合我们的期望,它与render
类似,不同的地方在于无论path是否匹配,都会被触发,然后会将当前的match信息传递过来,我们也正好可以通过match来控制CSSTransition
先写一个无动画的路由切换
不管怎么样,我们先写个简单的路由切换,然后再对其进行改装
主路由
<Router>
<div className="router4-transition">
<div className="nav">
<NavLink to="/" exact className="nav-item" activeClassName="active">
home
</NavLink>
<NavLink to="/page1" className="nav-item" activeClassName="active">
page1
</NavLink>
<NavLink to="/page2" className="nav-item" activeClassName="active">
page2
</NavLink>
</div>
<div className="pages">
<Route
path="/"
exact
component={props => {
if(!props.match) return null
return <Index />
}}
/>
<Route
path="/page1"
children={props => {
if(!props.match) return null
return <Page1 />
}}
/>
<Route
path="/page2"
children={props => {
if(!props.match) return null
return <Page2 />
}}
/>
</div>
</div>
</Router>
Index
class Index extends Component {
render() {
return <div className="page index">index</div>
}
}
Page1
class Page1 extends Component {
render() {
return <div className="page page1">page1</div>
}
}
Page2
class Page2 extends Component {
render() {
return <div className="page page2">page2</div>
}
}
简单的路由就写好了,接下来考虑加动画
利用高阶组件给页面加上动画
我并不希望在页面内部实现动画逻辑,首先是页面逻辑与动画逻辑无关,其次如果每写一个页面就写一次动画逻辑,我怕是要累死,所以我们这里将动画逻辑单独抽取出来,封装成一个高阶组件
function wrapAnimation(WrappedComponent) {
return class extends Component {
render() {
return (
<CSSTransition
in={this.props.match !== null}
classNames={{
enter: 'animated',
enterActive: 'fadeInDown',
exit: 'animated',
exitActive: 'fadeOutDown'
}}
timeout={1000}
mountOnEnter={true}
unmountOnExit={true}
>
<WrappedComponent {...this.props} />
</CSSTransition>
)
}
}
}
也是没啥可讲的代码,接下来,我们将我们的页面Index, Page1, Page2外面套一层高阶组件
const Index = wrapAnimation(
class Index extends Component {
render() {
return <div className="page index">index</div>
}
}
)
const Page1 = wrapAnimation(
class Page1 extends Component {
render() {
return <div className="page page1">page1</div>
}
}
)
const Page2 = wrapAnimation(
class Page2 extends Component {
render() {
return <div className="page page2">page2</div>
}
}
)
ok,然后再稍微修改下我们的路由层
<Router>
<div className="router4-transition">
<div className="nav">
<NavLink to="/" exact className="nav-item" activeClassName="active">
home
</NavLink>
<NavLink to="/page1" className="nav-item" activeClassName="active">
page1
</NavLink>
<NavLink to="/page2" className="nav-item" activeClassName="active">
page2
</NavLink>
</div>
<div className="pages">
<Route
path="/"
exact
children={props => {
return <Index {...props} />
}}
/>
<Route
path="/page1"
children={props => {
return <Page1 {...props} />
}}
/>
<Route
path="/page2"
children={props => {
return <Page2 {...props} />
}}
/>
</div>
</div>
</Router>
接下来,直接看效果吧
router4-transition.gif
总结
和普通的组件切换动画差不多,只是需要注意下怎么在react-router4的路由中获取组件的显示状态
完整的代码我放到了github上: https://github.com/soyal/router4-transition
感谢你的阅读
网友评论