基于ReactCSSTransitionGroup实现react-router过渡动画
此前,我使用了react-router库来完成单页应用的路由,从而实现组件之间的切换能力。然而,默认页面的切换是非常生硬的,为了让页面切换更加缓和与舒适,通常的方案就是过渡动画。
这里我调研了2种实现方案,它们都能够为react-router实现路由切换时的过渡效果,第1种是react官方自带的ReactCSSTransitionGroup(官方,推荐),第2种则是react-router-transition(非官方)。
下面,我会基于ReactCSSTransitionGroup来分析页面过渡的简单原理以及编程细节,而react-router-transition则大同小异,因此不做赘述。
ReactCSSTransitionGroup
安装
这个库是react官方自带的,它实现于react/lib/ReactCSSTransitionGroup.js。
你可以通过import直接导入这个文件,或者通过命令来安装一个便捷的别名包(仅仅是指向react/lib/ReactCSSTransitionGroup.js):
- npm install –save react-addons-css-transition-group
原理
ReactCSSTransitionGroup也是一个react组件,我们将在react-router的路由容器组件中引用它,让它替我们在路由切换的时候实现页面间的过渡动画。
首先看一下我的路由配置:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
ReactDOM.render(
(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Container}>
<IndexRoute component={MsgListPage} />
<Route path="msg-list-page" component={MsgListPage}/>
<Route path="msg-detail-page/:msgId" component={MsgDetailPage}/>
<Route path="msg-create-page" component={MsgCreatePage}/>
<Route path="menu-page" component={MenuPage}/>
</Route>
</Router>
</Provider>
),
document.getElementById('reactRoot')
);
|
一个很简单的路由配置,所有子路由的父容器都是Container组件,路由切换时react-router会将代表子路由的组件(例如MsgListPage)填充到Container的props.children孩子属性中。
既然Container组件是容纳子路由组件的容器,那么可以想到当子路由切换时:Conainter的props.children经历了从老的组件变为了新的组件的过程,如果可以在这个过程中稍作手脚是有机会实现新老组件的平滑过渡的。
先来看一下当前Container当前实现:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import React from "react";
export default class Container extends React.Component {
constructor(props, context) {
super(props, context);
}
componentWillMount() {
document.body.style.margin = "0px";
// 这是防止页面被拖拽
document.body.addEventListener('touchmove', (ev) => {
ev.preventDefault();
});
}
render() {
return (
<div id="reactContainer">
{
this.props.children
}
</div>
);
}
}
|
它将子路由组件(也就是this.props.children)直接填充了进来,这样实现虽然能够完成路由切换,但是它没有任何的过渡效果。
下面利用ReactCSSTransitionGroup实现过渡效果,代码变成了这样:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import React from "react";
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
import style from "./Container.css";
export default class Container extends React.Component {
constructor(props, context) {
super(props, context);
}
componentWillMount() {
document.body.style.margin = "0px";
// 这是防止页面被拖拽
document.body.addEventListener('touchmove', (ev) => {
ev.preventDefault();
});
}
render() {
return (
<ReactCSSTransitionGroup
transitionName="transitionWrapper"
component="div"
className={style.transitionWrapper}
transitionEnterTimeout={300}
transitionLeaveTimeout={300}>
<div key={this.props.location.pathname}
style={{position:"absolute", width: "100%"}}>
{
this.props.children
}
</div>
</ReactCSSTransitionGroup>
);
}
}
|
我们直接套用了ReactCSSTransitionGroup组件,并将子路由组件(this.props.children)包裹在其内部,这样做的目的是:当子路由组件切换时,ReactCSSTransitionGroup可以拦截其内部新老组件的交替过程,从而实现老组件消逝,新组件出现的过渡视觉。
说了那么多,不如看一下切换路由的瞬间DOM树的样子,更加便于理解:

外层div是ReactCSSTransitionGroup引入的父<div>,它内部是有2个子<div>是这段代码引入的:
|
1
2
|
<div key={this.props.location.pathname}
style={{position:"absolute", width: "100%"}}>
|
默认同一时刻应该只有1个路由组件,那么<div>为什么会出现2个呢?因为ReactCSSTransitionGroup拦截了子路由切换的过程,它在组件替换前将前1个子组件备份了起来,在替换后将新老2个子组件一起填充到父<div>中并开始执行过渡动画,当动画结束后它将老组件移除只保留下新组件:

为什么子<div>要有一个key属性呢?因为ReactCSSTransitionGroup在过渡期间同时维护新老组件需要一个唯一标识加以区分,因为location.pathname代表当前访问的完整路径(包括_k=…),所以用它最合适不过。
CSS动画
至于动画是怎么实现的?第一张图片里你应该可以看到,它为2个子<div>添加了对应的class,一个是enter进入的意思,另外一个是leave离开的意思,我们只需要定义对应的css实现transition动画既可(注意<ReactCSSTransitionGroup>的transitionName属性定义了下述class的前缀):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
:global(.transitionWrapper-enter) {
opacity: 0.01;
transition: opacity 30000ms ease-in;
}
:global(.transitionWrapper-enter.transitionWrapper-enter-active) {
opacity: 1;
}
:global(.transitionWrapper-leave) {
opacity: 1;
transition: opacity 30000ms ease-in;
}
:global(.transitionWrapper-leave.transitionWrapper-leave-active) {
opacity: 0;
}
.transitionWrapper {
position: relative;
}
|
这里,:global(classname)的用法是css-loader插件提供的,默认所有css都是通过css-loader局部编译的,从而保证跨组件css名字不冲突。
然而ReactCSSTransitionGroup组件不支持我们控制这些动画class的命名规则,因此我们只能使用全局css,通过:global修饰的class或者id都不会被编码,而是在整个app全局生效,这一块知识可以在这里补充学习。
这里我基于transition实现透明度opacity的动画,新组件逐渐显现而老组件逐渐淡化,动画方面可以自行学习。这里重点提一下:
|
1
|
.transitionWrapper-enter.transitionWrapper-enter-active
|
我们通常见过2种css表达:
- .class1 .class2,中间是一个空格,表示class1孩子里的class2元素都应用某css规则。
- .class1,.class2,中间是一个逗号,表示class1和class2都应用某css规则。
这里.class1.class2是连续写的,表示同时满足class1和class2的元素应用css规则。
为什么不好用?
很多朋友用ReactCSSTransitionGroup发现路由切换动画异常,不符合预期的效果,怎么调试都不行,其实本质都是对原理不够了解。
问题关键在于CSS控制有问题,如果你理解了上述ReactCSSTransitionGroup实现的原理,那么你应该知道新老组件同时出现的时候属于过渡阶段,它们顺序堆积在父<div>中(默认<div>是从上而下堆砌的)。
为了实现过渡效果,理所应当让2个组件重叠在屏幕中央,然后一个淡入一个淡出。因此这就要求子组件要绝对定位(position:absolute),因此你可以看到我给transitionWrapper应用了position:relative,并给<div key=…>应用了position:absolute,width:100%,就是这个道理。
为什么报错?
如果你发现console里有这样的报错:
Warning: setState(…): Cannot update during an existing state transition (such as within
renderor another component’s constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved tocomponentWillMount.
那么说明你在组件的render或者constructor里调用了setState方法,这些应该移到componentWillMount中执行。
我用的是react-redux,之前的某些组件在构造函数里调用了action触发了state修改也被警告了,因此我将初始化组件用的action调用挪到了componentWillMount中,问题迎刃而解。
体验
代码:https://github.com/owenliang/react
扫码访问:

基于ReactCSSTransitionGroup实现react-router过渡动画的更多相关文章
- 基于 React 实现一个 Transition 过渡动画组件
过渡动画使 UI 更富有表现力并且易于使用.如何使用 React 快速的实现一个 Transition 过渡动画组件? 基本实现 实现一个基础的 CSS 过渡动画组件,通过切换 CSS 样式实现简单的 ...
- react过渡动画效果的实现,react-transition-group
本文介绍react相关的过渡动画效果的实现 有点类似vue的transition组件,主要用于组件mount和unmount之前切换时应用动画效果 安装 cnpm install react-tran ...
- 12 react 基础 的 css 过渡动画 及 动画效果 及 使用 react-transition-group 实现动画
一. 过渡动画 # index.js import React from 'react';import ReactDOM from 'react-dom';import App from './app ...
- React Router教程
React Router教程 React项目的可用的路由库是React-Router,当然这也是官方支持的.它也分为: react-router 核心组件 react-router-dom 应用于浏览 ...
- React Router API文档
React Router API文档 一.<BrowserRouter> 使用HTML5历史记录API(pushState,replaceState和popstate事件)的<Rou ...
- React Router学习
React Router教程 本教程引用马伦老师的的教程 React项目的可用的路由库是React-Router,当然这也是官方支持的.它也分为: react-router 核心组件 react-ro ...
- React+React Router+React-Transition-Group实现页面左右滑动+滚动位置记忆
2018年12月17日更新: 修复在qq浏览器下执行pop跳转时页面错位问题 本文的代码已封装为npm包发布:react-slide-animation-router 在React Router中,想 ...
- 最新的chart 聊天功能( webpack2 + react + router + redux + scss + nodejs + express + mysql + es6/7)
请表明转载链接: 我是一个喜欢捣腾的人,没事总喜欢学点新东西,可能现在用不到,但是不保证下一刻用不到. 我一直从事的是依赖angular.js 的web开发,但是我怎么能一直用它呢?看看最近火的一塌糊 ...
- React router动态加载组件-适配器模式的应用
前言 本文讲述怎么实现动态加载组件,并借此阐述适配器模式. 一.普通路由例子 import Center from 'page/center'; import Data from 'page/data ...
随机推荐
- oracle中批量生成字段类型的脚本
select ' ALTER TABLE '||table_name || ' MODIFY( '|| column_name || ' DATE );' from USER_TAB_COLUMNS ...
- Fancybox丰富的弹出层效果
Fancybox是一款优秀的jquery插件,它能够展示丰富的弹出层效果.前面我们有文章介绍了facybox弹出层效果,相比facybox,fancybox显得功能更为齐全,它除了可以加载DIV,图片 ...
- 该不该在C#中使用var关键词
作为一个并不勤快的程序猿,在项目开发过程中总是想尽办法少写代码,对var关键词的使用自然不会放过,几乎在每个能使用var的地方都用了var,对此,很多朋友同事给出了不同的建议,觉得能不使用var关键词 ...
- C程序语法(无左递归)
<程序> -〉 <外部声明> | <函数定义><外部声明> -〉<头文件> | <变量> | <结构体> <头 ...
- SpringMvc的xml配置与annotation配置的例子的区别
1.导入jar包时,要在xml配置基础上加 spring-aop-4.2.2.RELEASE.jar (注解的时候需要) 2.编写controller的时候要annotation需要做相关配置即红色部 ...
- vim 插件之 gist.vim 的安装
用 IntelliJ 的时觉得 create gist 很好用,查了下,发现 vim 下也有这个插件,于是马上配置上. 安装 下载 Gist.vim 解压后进入目录,拷贝文件 cp plugin/gi ...
- python面试总结
1.python的优势 1.1 python是一门胶水语言,能够结合各种语言 1.2 python是支持面向对象编程 1.3 python是完全开放源代码,有大量的技术支持文档, 1.4 可移植,py ...
- 给li标签添加自定义属性
给li标签添加属性<ul> <li></li> <li></li> <li></li> <li>< ...
- 实现Android桌面的App快捷方式
本文描述的是,在App开发过程中,该如何实现App在Anroid桌面上生成App的快捷方式.主要分为两个步骤: 一,在AndroidManifest.xml中声明相关权限: <uses-perm ...
- USER STORIES AND USE CASES - DON’T USE BOTH
We’re in Orlando for a working session as part of the Core Team building BABOK V3 and over dinner th ...