此前,我使用了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 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 to componentWillMount.

那么说明你在组件的render或者constructor里调用了setState方法,这些应该移到componentWillMount中执行。

我用的是react-redux,之前的某些组件在构造函数里调用了action触发了state修改也被警告了,因此我将初始化组件用的action调用挪到了componentWillMount中,问题迎刃而解。

体验

代码:https://github.com/owenliang/react

扫码访问:

基于ReactCSSTransitionGroup实现react-router过渡动画的更多相关文章

  1. 基于 React 实现一个 Transition 过渡动画组件

    过渡动画使 UI 更富有表现力并且易于使用.如何使用 React 快速的实现一个 Transition 过渡动画组件? 基本实现 实现一个基础的 CSS 过渡动画组件,通过切换 CSS 样式实现简单的 ...

  2. react过渡动画效果的实现,react-transition-group

    本文介绍react相关的过渡动画效果的实现 有点类似vue的transition组件,主要用于组件mount和unmount之前切换时应用动画效果 安装 cnpm install react-tran ...

  3. 12 react 基础 的 css 过渡动画 及 动画效果 及 使用 react-transition-group 实现动画

    一. 过渡动画 # index.js import React from 'react';import ReactDOM from 'react-dom';import App from './app ...

  4. React Router教程

    React Router教程 React项目的可用的路由库是React-Router,当然这也是官方支持的.它也分为: react-router 核心组件 react-router-dom 应用于浏览 ...

  5. React Router API文档

    React Router API文档 一.<BrowserRouter> 使用HTML5历史记录API(pushState,replaceState和popstate事件)的<Rou ...

  6. React Router学习

    React Router教程 本教程引用马伦老师的的教程 React项目的可用的路由库是React-Router,当然这也是官方支持的.它也分为: react-router 核心组件 react-ro ...

  7. React+React Router+React-Transition-Group实现页面左右滑动+滚动位置记忆

    2018年12月17日更新: 修复在qq浏览器下执行pop跳转时页面错位问题 本文的代码已封装为npm包发布:react-slide-animation-router 在React Router中,想 ...

  8. 最新的chart 聊天功能( webpack2 + react + router + redux + scss + nodejs + express + mysql + es6/7)

    请表明转载链接: 我是一个喜欢捣腾的人,没事总喜欢学点新东西,可能现在用不到,但是不保证下一刻用不到. 我一直从事的是依赖angular.js 的web开发,但是我怎么能一直用它呢?看看最近火的一塌糊 ...

  9. React router动态加载组件-适配器模式的应用

    前言 本文讲述怎么实现动态加载组件,并借此阐述适配器模式. 一.普通路由例子 import Center from 'page/center'; import Data from 'page/data ...

随机推荐

  1. Day 1 T1

    题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业. 有一天, 这些玩具小人把小南的眼镜藏了起来. 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外.如下图: 这时singer告诉 ...

  2. LNMP虚拟机开发环境配置--vagrant+virtualbox+ubuntu14.04

    工作一直用的是别人打包好的虚拟机开发环境,感觉确实很酷.所以准备自己配个开发环境,为之后自己开发一些有趣的东西做准备. ok,开始~~~ 一.安装软件 vagrant和virtualbox 此处需注意 ...

  3. 分享一个discuz touch端的jQuery下拉刷新组件

    在线Demo 最近装了个discuz论坛, 趣股VIP吧,发现里面内置的jQuery上拉刷新组件写得还行,STATICURL可以用'http://o9gzet7tk.bkt.clouddn.com/i ...

  4. 【转】iOS开发 -- Apple Pay

    技术博客原地址:http://www.cnblogs.com/dashunzi/p/ApplePay.html#top 原技术博客中有源码和视频,有感兴趣的朋友可以研究一下! 一.什么是Apple P ...

  5. SOAPUI使用教程-REST功能测试

    当创造了SoapUI功能测试用例,常见的情况是,你调用一些REST资源和验证其响应检查返回正确的结果.这可以容易地实现: 添加一个REST请求到新的test step或现有的TestCase 添加断言 ...

  6. (学)解决诡异的 Exception type: SocketException 127.0.0.1:80

    许久不发博了,老杨听完故事让我持续写一下“十万个为什么” 一.背景:  昨天我们亲密的战友HH刘老板亲临现场,指出我们协用的一个项目,客户方面反馈手持终端系统不定期“卡死”,要我们安排人飞到广州驻场解 ...

  7. Xilinx下载安装与在win10闪退问题解决方法

    Xilinx的14.4版本的下载链接(百度云的上传了N多次都提示失败,所以就换了360云盘上传) https://yunpan.cn/cPHKLjbX9RueM (提取码:2a5a)下载后解压到以下目 ...

  8. International Conference for Smart Health 2015 Call for Papers

    Advancing Informatics for healthcare and healthcare applications has become an international researc ...

  9. PHP好任性 —— 大小写敏感有两种规则,然而并没有什么特别原因

    大小写敏感 变量.常量大小写敏感 大小写不敏感 类名.方法名.函数名.魔法变量大小写不敏感 原因 有人原引了Rasmus 在一次会议上的发言大意: "I'm definitely not a ...

  10. GridControl读取xml和保存xml

    using DevExpress.XtraGrid;// ...string fileName ="c:\\XtraGrid_SaveLayoutToXML.xml"; priva ...