此前,我使用了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
扫码访问:
-
在本篇进阶指南的第二部分中,我们将继续探讨 React 与 Webpack 的高级配置技巧。通过实际案例,我们将展示如何使用 React 和 Webpack 构建一个简单的 Todo 应用程序,具体包括 `TodoApp.js` 文件中的代码实现,如导入 React 和自定义组件 `TodoList`。此外,我们还将深入讲解 Webpack 配置文件的优化方法,以提升开发效率和应用性能。 ... [详细]为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]如何使用ES6语法编写Webpack配置文件? ... [详细]Spring – Bean Life Cycle ... [详细]在处理大规模数据数组时,优化分页组件对于提高页面加载速度和用户体验至关重要。本文探讨了如何通过高效的分页策略,减少数据渲染的负担,提升应用性能。具体方法包括懒加载、虚拟滚动和数据预取等技术,这些技术能够显著降低内存占用和提升响应速度。通过实际案例分析,展示了这些优化措施的有效性和可行性。 ... [详细]Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]在 Vue 应用开发中,页面状态管理和跨页面数据传递是常见需求。本文将详细介绍 Vue Router 提供的两种有效方式,帮助开发者高效地实现页面间的数据交互与状态同步,同时分享一些最佳实践和注意事项。 ... [详细]使用 Vuex 管理表单状态:当输入框失去焦点时自动恢复初始值 ... [详细]Vue应用预渲染技术详解与实践 ... [详细]在HTML页面中,通过调用JavaScript函数生成随机数值,并将其自动展示在页面上。具体实现包括构建HTML页面结构,定义JavaScript函数以生成随机数,以及在页面加载时自动调用该函数并将结果呈现给用户。 ... [详细]在深入研究 React 项目的过程中,特别是在探索 react-router 源码时,我发现了其中蕴含的中间件概念。这激发了我对中间件的进一步思考与整理。本文将详细探讨 Redux 中间件的原理及其在实际项目中的应用,帮助读者更好地理解和使用这一强大工具。通过具体示例和代码解析,我们将揭示中间件如何提升应用的状态管理和异步操作处理能力。 ... [详细]React项目基础教程第五课:深入解析组件间通信机制 ... [详细]深入解析 Vue3 中的响应式 API:shallowReactive、shallowRef、triggerRef 和 customRef 的使用与原理 ... [详细]Tags | 热门标签RankList | 热门文章
- 1力扣第21题:Java与Python实现有序链表合并(基础难度)
- 2解决 Fetch 请求扇贝 API 时遇到的跨域问题及优化方案
- 3安卓应用中实现界面根据设备方向自动切换横屏与竖屏的功能优化
- 4WebKit Tap Highlight Color 的默认样式设置
- 5下篇:APP端登录流程详解——前置知识与技术准备
- 6《空屋诗》译文解析与唐元稹原作赏析
- 7jQuery插件验证与屏幕键盘功能的集成解决方案
- 8通过 NuGet 获取最新版本的 Rafy 框架及其详细文档
- 9设计模式详解:模板方法模式的应用与实现
- 10Java SE 文件操作类详解与应用
- 11技术分享:深入解析GestureDetector手势识别机制
- 12实现Nginx对ThinkPHP URL重写及PATHINFO支持的详细方法解析【PHP开发】
- 132021年10月21日工作日志记录
- 14智能锁具:安全与便捷的完美结合
- 15探索聚类分析中的K-Means与DBSCAN算法及其应用