此前,我使用了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引入的父
1
2
|
<div key={this.props.location.pathname}
style={{position:"absolute", width: "100%"}}>
|
默认同一时刻应该只有1个路由组件,那么
为什么子
CSS动画
至于动画是怎么实现的?第一张图片里你应该可以看到,它为2个子
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实现的原理,那么你应该知道新老组件同时出现的时候属于过渡阶段,它们顺序堆积在父
为了实现过渡效果,理所应当让2个组件重叠在屏幕中央,然后一个淡入一个淡出。因此这就要求子组件要绝对定位(position:absolute),因此你可以看到我给transitionWrapper应用了position:relative,并给
为什么报错?
如果你发现console里有这样的报错:
Warning: setState(…): Cannot update during an existing state transition (such as within
render
or 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
扫码访问:
-
本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]本文介绍了如何使用vue-awesome-swiper组件,包括在main.js中引入和使用swiper和swiperSlide组件,以及设置options和ref属性。同时还介绍了如何在模板中使用swiper和swiperSlide组件,并展示了如何通过循环渲染swipes数组中的数据,并使用picUrl属性显示图片。最后还介绍了如何添加分页器。 ... [详细]本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]本文介绍了MVP架构模式及其在国庆技术博客中的应用。MVP架构模式是一种演变自MVC架构的新模式,其中View和Model之间的通信通过Presenter进行。相比MVC架构,MVP架构将交互逻辑放在Presenter内部,而View直接从Model中读取数据而不是通过Controller。本文还探讨了MVP架构在国庆技术博客中的具体应用。 ... [详细]本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]本文介绍了Node.js中包的概念,以及如何使用包来统一管理具有相互依赖关系的模块。同时还介绍了NPM(Node Package Manager)的基本介绍和使用方法,以及如何通过NPM下载第三方模块。 ... [详细]本文介绍了2015年九月八日的js学习总结及相关知识点,包括参考书《javaScript Dom编程的艺术》、js简史、Dom、DHTML、解释型程序设计和编译型程序设计等内容。同时还提到了最佳实践是将标签放到HTML文档的最后,并且对语句和注释的使用进行了说明。 ... [详细]本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]本文介绍了Python字典视图对象的示例和用法。通过对示例代码的解释,展示了字典视图对象的基本操作和特点。字典视图对象可以通过迭代或转换为列表来获取字典的键或值。同时,字典视图对象也是动态的,可以反映字典的变化。通过学习字典视图对象的用法,可以更好地理解和处理字典数据。 ... [详细]Tags | 热门标签RankList | 热门文章
- 1Django单元测试你用factory_boy还是model_mommy,为什么?
- 2接口中变量和方法的默认修饰符是什么
- 3安卓版家庭背景音乐系统,让生活掷地有声
- 4讲讲我在外包华三的面试经历
- 5CDN服务的欠费说明
- 6拍抖音,如何有背景音乐又有自己声音?
- 7reactrouter V4 ,页面跳转问题
- 8第09章 返回咸阳
- 9温度上报到腾讯云物联网通信
- 102021/11/3模拟赛
- 11什么是windows10(转载)
- 12黑客恶意修改化学成分参数,远程投毒饮用水
- 13Box2d源码学习二内存管理之SOA的实现
- 14年终的第一篇总结 结束南漂 写在2017
- 15图解数据交换技术——电路交换、报文交换、分组交换