├── .gitignore
├── README.md
├── docs
├── .nojekyll
├── README.md
├── _coverpage.md
├── _navbar.md
├── _sidebar.md
├── house
│ ├── index.md
│ ├── longgang.md
│ ├── src
│ │ ├── lg_2_2.jpg
│ │ ├── lg_3_1.jpg
│ │ ├── lg_3_2.jpg
│ │ └── lg_3_3.jpg
│ └── target.md
├── index.html
├── readmyself.md
└── src
│ ├── index.md
│ ├── resources
│ ├── no-animation.gif
│ ├── present-animation.gif
│ └── push-animation.gif
│ ├── router.md
│ └── router_how.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.js
├── RouterConfig.js
├── index.css
├── index.js
├── pages
│ ├── Detail.js
│ ├── Home.js
│ ├── HybridPage.js
│ ├── PopPage.js
│ ├── ReplacePage.js
│ ├── components
│ │ ├── Button.js
│ │ ├── Title.js
│ │ ├── Toast.js
│ │ └── index.js
│ └── index.js
├── router
│ ├── AnimatedSwitch.css
│ ├── AnimatedSwitch.js
│ ├── Router.js
│ ├── StackSwitch.css
│ ├── StackSwitch.js
│ └── WrapedComp.js
└── serviceWorker.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## react-router-virgo 使用手册
2 |
3 | One line of code and one route configuration file, realize the function of `react-router`, and match the transition animation effect of native route.
4 |
5 | 一行代码 + 一个路由配置文件,就可以实现 `react-router` 的功能,并让你的 Web 页面切换媲美 Native 路由的转场动画体验
6 |
7 | > 这里主要介绍怎么使用react-router-virgo,对实现原理感兴趣的小伙伴,可以去看我的这篇[技术博客](https://juejin.im/post/5eec73a5518825658d034976)
8 |
9 | ----
10 |
11 | ### 一、Brief Introduction 概述
12 |
13 | > [react-router-virgo](https://github.com/JackXJR/react-router-virgo) is a router with secondary encapsulation based on `react-router`, which makes the integrated routing function extremely simple. In addition, the extended functions such as route transition animation are added: `No transition animation`, `push transition animation`, `present transition animation`
14 | >
15 | > [react-router-virgo](https://github.com/JackXJR/react-router-virgo)是在 react-router 的基础上进行二次封装的 Router,使集成路由功能变得极其简单。此外,还增加了路由转场动画等扩展功能:`无转场动画`, `Push 转场动画`, `Present 转场动画`,我们先来看看效果吧
16 |
17 | - 无转场动画(现有的 web 页面切换体验)
18 |
19 |
20 |
21 | - push 动画切换(右侧淡入,右侧淡出)
22 |
23 |
24 |
25 | - present 动画切换(下方淡入,下方淡出的模态切换)
26 |
27 |
28 |
29 | > 总体上,基本能达到 native 路由的切换体验,当然,毕竟是 web 页面,相对于 iOS 的原生的 native 路由体验还是有点差距,感兴趣的可以自己运行完整 demo 体验下。。。
30 |
31 |
32 | ### 二、Installation 安装
33 |
34 | ```
35 | ### use npm
36 | ### 使用npm
37 | npm install --save react-router-virgo
38 |
39 | ### use yarn
40 | ### 使用yarn
41 | yarn add react-router-virgo
42 | ```
43 |
44 | ### 三、RouterConfig 设置路由配置文件
45 |
46 | > Set route profile `RouterConfig.js`
47 | >
48 | > 设置路由配置文件 `RouterConfig.js`
49 |
50 | #### 3.1 Example 代码演示
51 |
52 | ```javascript
53 | import { Home, Detail } from './pages/index'
54 |
55 | // Three scenes are exemplified: no animation, push transition animation and present transition animation
56 | // 例举了无动画、push转场动画、present转场动画三种场景
57 | export const RouterConfig = [
58 | { path: '/', component: Home },
59 | // No transition animation (the new page directly covers the current page)
60 | // 无转场动画(新页面直接覆盖当前页面)
61 | {
62 | path: '/detail',
63 | component: Detail,
64 | sceneConfig: {
65 | enter: 'no-animation',
66 | exit: 'no-animation',
67 | },
68 | },
69 | // Push transition animation (when opening the page, overwrite from left to right; when closing the page, withdraw from right to left)
70 | // Push 转场动画(打开页面时,从左往右覆盖;关闭页面时,从右往左收回)
71 | {
72 | path: '/push/detail',
73 | component: Detail,
74 | sceneConfig: {
75 | enter: 'from-right',
76 | exit: 'to-right',
77 | },
78 | },
79 | // Present transition animation (when opening the page, it will pop up from the bottom; when closing the page, it will pop up from the top)
80 | // Present 转场动画(打开页面时,从下往上弹起;关闭页面时,从上往下收起)
81 | {
82 | path: '/present/detail',
83 | component: Detail,
84 | sceneConfig: {
85 | enter: 'from-bottom',
86 | exit: 'to-bottom',
87 | },
88 | },
89 | ]
90 | ```
91 |
92 | #### 3.2 RouterConfig Description 配置项说明
93 |
94 | | key | description | type | default | |
95 | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------- | ---------- |
96 | | `path` | The routing path can take parameters. The one after `/:` is a parameter, such as `/ detail /: id`. The parameter is id. 路由路径,可以带参数,在`/:`后的为参数,如 `/detail/:id`, 参数为 id | string | | `Required` |
97 | | `component` | Page components of route path mapping. 路由路径映射的页面组件 | class | | `Required` |
98 | | `sceneConfig` | The route transition animation configuration supports three scenes: `no animation`, `push animation` and `present animation`. The default is push animation. 路由转场动画配置,支持`无动画`、`Push 动画`、`Present 动画`三种场景,默认使用 Push 动画 | object | `{enter: 'from-right', exit: 'to-right'}` | `Optional` |
99 | | `exact` | Use exact match or not. 是否使用精准匹配 | bool | true | `Optional` |
100 |
101 | - Routing transition animation parameter `sceneConfig` configuration supports the following three scenarios
102 | - 路由转场动画参数`sceneConfig`配置,支持以下三种场景
103 |
104 | ```
105 | ### No animation configuration
106 | ### 无动画配置
107 | {
108 | enter: 'no-animation',
109 | exit: 'no-animation',
110 | }
111 |
112 | ### Push animation configuration
113 | ### Push动画配置
114 | {
115 | enter: 'from-right',
116 | exit: 'to-right',
117 | }
118 |
119 | ### Present animation configuration
120 | ### Present动画配置
121 | {
122 | enter: 'from-bottom',
123 | exit: 'to-bottom',
124 | }
125 | ```
126 |
127 | ### 四、Use Router 使用路由
128 |
129 | > Add `Router` to entry file `App.js`
130 | >
131 | > 在入口文件`App.js`中添加`Router`
132 |
133 | #### 4.1 Example 代码演示
134 |
135 | ```javascript
136 | import React from 'react'
137 | import Router from './router/Router'
138 | import { RouterConfig } from './RouterConfig'
139 | import './index.css'
140 |
141 | function App() {
142 | return
143 | }
144 |
145 | export default App
146 | ```
147 |
148 | #### 4.2 API
149 |
150 | | props | description | type | default | |
151 | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----- | ------- | ---------- |
152 | | `routerConfig` | Route configuration data. 路由配置数据 | array | [] | `Required` |
153 | | `useBrowserRouter` | Routing type `BrowserRouter/HashRouter`. By default, HashRouter is used. 路由类型 `BrowserRouter/HashRouter`,默认使用 `HashRouter` | bool | false | `Optional` |
154 | | `useAnimatedSwitch` | Use transition animation or not. 是否使用转场过渡动画 | bool | true | `Optional` |
155 |
156 | ### 五. FAQ 常见问题
157 |
158 | > Q: Which routing types are supported?
159 | > 支持哪些路由类型?
160 |
161 | - Two types of `BrowserRouter` and `HashRouter` are supported. They can be set by the property `useBrowserRouter`. The default is HashRouter
162 | - 目前支持 BrowserRouter 和 HashRouter 两种类型,可通过属性`useBrowserRouter`来设置,默认使用 HashRouter
163 |
164 | > Q: What transition animations are supported?
165 | > 支持哪些转场动画?
166 |
167 | - It supports three kinds of scenes: no animation, pop-up presentation animation, and right to left push animation. You can configure `sceneConfig` according to rules in the routing configuration file. If the sceneConfig field is not configured, push animation is used by default
168 | - 目前支持无动画、从下往上弹起的 Present 动画,从右往左打开的 Push 动画三种场景。可以在路由配置文件中按规则配置`sceneConfig`即可,如果未配置 sceneConfig 字段,则默认使用 Push 动画
169 |
170 | > Q: After opening a new page, will the previous page be destroyed?
171 | > 打开新页面后,上一级页面是否会被销毁?
172 |
173 | - It will be destroyed. When returning to the previous page, the page will be re rendered, and the subsequent version iterations will support the stack routing function.
174 | - 会被销毁,返回上一级页面时,页面会重新渲染,后续版本迭代会支持 Stack 路由功能。
175 |
176 | > Q:Why does compilation fail after installation?
177 | > 安装后,编译失败的原因?
178 |
179 | - Confirm whether there are two dependencies `react-router-dom` and `react-transition-group` in your project. If not, please import the dependency through yarn or NPM.
180 | - 确认下项目里是否有 `react-router-dom`、`react-transition-group` 这 2 个依赖,如果没有,请通过 yarn 或者 npm 引入依赖.
181 |
182 | ```
183 | yarn add react-router-dom react-transition-group
184 | ```
185 |
186 | > Q: Whether the project developed by TS is supported
187 | > 是否支持 ts 开发的项目
188 |
189 | - Subsequent iterations will support
190 | - 后续迭代会支持
191 |
192 | ### 六. More 其它
193 |
194 | If you are interested in more specific information, go to the code. If you find a bug, please mention an [issue](https://github.com/JackXJR/react-router-virgo/issues), I will repair and optimize it as soon as possible...
195 |
196 | 更具体的信息大家感兴趣的话去看代码吧,如果发现 bug,请提一个[issue](https://github.com/JackXJR/react-router-virgo/issues),我会第一时间进行修复和优化...
197 |
198 | > Welcome to use, if you think it's good, please give a little `star` encouragement~
199 | >
200 | > 欢迎使用,觉得不错请给一个小小的 `star` 鼓励一下~
201 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ### 路由 Router
2 |
3 | 项目中的路由使用[react-router-virgo](https://github.com/JackXJR/react-router-virgo)中的 Router,可以实现 native 路由的转场动画体验。
4 |
5 | - [react-router-virgo 使用手册](/src/router.md)
6 |
7 | - [react-router-virgo 原理解析](/src/router_how.md)
8 |
--------------------------------------------------------------------------------
/docs/_coverpage.md:
--------------------------------------------------------------------------------
1 | # 谷底飞龙
2 |
3 | 本文档主要针对基于 React-Router 封装含转场过渡动画的 Router 的使用及原理进行详细介绍!希望能给你带来帮助!
4 |
5 | > 如果觉得对你有所帮助,请帮忙给个 [Star](https://github.com/JackXJR/react-router-virgo)!
6 |
7 | [GitHub](https://github.com/JackXJR/react-router-virgo)
8 | [Get Started](README.md)
9 |
--------------------------------------------------------------------------------
/docs/_navbar.md:
--------------------------------------------------------------------------------
1 | - [自述](/readmyself.md)
2 |
3 | - [react-router-virgo](/src/index.md)
4 | - [react-route-virgo 使用手册](/src/router.md)
5 | - [react-route-virgo 原理解析](/src/router_how.md)
6 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | - [自述](/readmyself.md)
2 |
3 | - [react-router-virgo](/src/index.md)
4 |
5 | - [react-route-virgo 使用手册](/src/router.md)
6 | - [react-route-virgo 原理解析](/src/router_how.md)
7 |
8 | - [深房投资攻略](/house/index.md)
9 | - [预期目标](/house/target.md)
10 | - [龙岗区](house/longgang.md)
11 |
--------------------------------------------------------------------------------
/docs/house/index.md:
--------------------------------------------------------------------------------
1 | # 深圳房产投资攻略
2 |
--------------------------------------------------------------------------------
/docs/house/longgang.md:
--------------------------------------------------------------------------------
1 | # 龙岗区
2 |
3 | ## 两室
4 |
5 | > 1.区域:[宝荷 中环至外环](https://sz.lianjia.com/ershoufang/105104494035.html)
6 |
7 | | 总价 | 单价 | 面积 | 朝向 | 楼龄 |
8 | | ------ | ----- | ---- | --------------------- | --------- |
9 | | 259 万 | 43312 | 59.8 | 东南/精装/电梯/高(33) | 2014/板塔 |
10 |
11 | 缺点:`无地铁/无学校`、`满两年`
12 |
13 | > 2.[大运新城 THETOWN 乐城](https://sz.lianjia.com/ershoufang/105103790167.html),两室一厅
14 |
15 | | 总价 | 单价 | 面积 | 朝向 | 楼龄 |
16 | | ------ | ----- | ----- | --------------------- | ------- |
17 | | 245 万 | 47426 | 51.66 | 东南/简装/电梯/中(33) | 2014/板 |
18 |
19 | 
20 |
21 | 优点:`地铁3号线荷坳站374米、大运站1215米`、`学校配套完善`
22 |
23 | 缺点:`满两年`
24 |
25 | ## 三室
26 |
27 | > 1.区域:[龙岗中心城 汇龙天下](https://sz.lianjia.com/ershoufang/105104136111.html),三室一厅
28 |
29 | | 总价 | 单价 | 面积 | 朝向 | 楼龄 |
30 | | ------ | ----- | ----- | ------------------- | --------- |
31 | | 290 万 | 32251 | 89.92 | 南/简装/电梯/高(26) | 2011/板塔 |
32 |
33 | 
34 |
35 | 优点:`地铁3号线龙城广场站1.3公里`、`满五唯一`、`学校配套完善`
36 |
37 | > 2.区域:[双龙 金汐府](https://sz.lianjia.com/ershoufang/105104341680.html),三室两厅
38 |
39 | | 总价 | 单价 | 面积 | 朝向 | 楼龄 |
40 | | ------ | ----- | ----- | --------------------- | --------- |
41 | | 279 万 | 35917 | 77.68 | 西南/精装/电梯/中(30) | 2015/板塔 |
42 |
43 | 
44 |
45 | 优点:`地铁3号线双龙站1.52公里`、`学校配套完善`
46 |
47 | > 3.区域:[坪地 中骏四季阳光二期](https://sz.lianjia.com/ershoufang/105104573589.html),三室两厅
48 |
49 | | 总价 | 单价 | 面积 | 朝向 | 楼龄 |
50 | | ------ | ----- | ----- | ------------------- | --------- |
51 | | 290 万 | 32251 | 84.26 | 南/毛胚/电梯/高(31) | 2016/板塔 |
52 |
53 | 
54 |
55 | 缺点:`无地铁`
56 |
--------------------------------------------------------------------------------
/docs/house/src/lg_2_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/house/src/lg_2_2.jpg
--------------------------------------------------------------------------------
/docs/house/src/lg_3_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/house/src/lg_3_1.jpg
--------------------------------------------------------------------------------
/docs/house/src/lg_3_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/house/src/lg_3_2.jpg
--------------------------------------------------------------------------------
/docs/house/src/lg_3_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/house/src/lg_3_3.jpg
--------------------------------------------------------------------------------
/docs/house/target.md:
--------------------------------------------------------------------------------
1 | # 预期目标
2 |
3 | > 投资预期
4 |
5 | | 总价/元 | 面积/m2 | 首付/元 | 月供/元 | 租金/元 |
6 | | ---------- | ------- | --------- | ---------- | --------- |
7 | | 200~300 万 | 50~90 | 65~100 万 | 0.7~1.1 万 | 3000~5000 |
8 |
9 | > 房屋要求
10 |
11 | | 房型 | 楼龄 | 楼层 | 朝向 | 位置 | 其它 |
12 | | ------- | ------ | ------ | ---- | -------------- | ---------------- |
13 | | 两/三室 | <15 年 | 中高层 | 朝南 | 地铁/学校/生活 | 满二/电梯/商品房 |
14 |
15 | > 费用计算
16 |
17 | | 中介费 | 契税 | 个税 | 增值税 |
18 | | ------ | ---- | ---- | ------ |
19 | | 1~2% | 1% | 0~1% | 0 |
20 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 谷底飞龙
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/docs/readmyself.md:
--------------------------------------------------------------------------------
1 | ### 作者简述
2 |
3 | > 笔名`谷底飞龙`! 投资生涯始于 2013 年,技术生涯始于 2014 年,致力于成为媲美巴菲特的价值投资者,业界顶尖的高水平大前端架构师!愿景是`成为投资界里的技术大牛,技术界里的投资大鳄!`
4 |
5 | ### 技术栈
6 |
7 | | iOS | 前端 | 小程序 | 跨平台 | 其它 |
8 | | --- | ----- | ------------- | ------------------- | ---------- |
9 | | OC | React | 微信/字节跳动 | ReactNative/Flutter | nodejs ... |
10 |
--------------------------------------------------------------------------------
/docs/src/index.md:
--------------------------------------------------------------------------------
1 | > [react-router-virgo](https://github.com/JackXJR/react-router-virgo)是基于 `react-router + react-transition-group` 封装的 Router,可以实现 native 路由的转场动画体验。
2 |
3 | - [react-router-virgo 使用手册](/src/router.md)
4 |
5 | - [react-router-virgo 原理解析](/src/router_how.md)
6 |
--------------------------------------------------------------------------------
/docs/src/resources/no-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/src/resources/no-animation.gif
--------------------------------------------------------------------------------
/docs/src/resources/present-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/src/resources/present-animation.gif
--------------------------------------------------------------------------------
/docs/src/resources/push-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/docs/src/resources/push-animation.gif
--------------------------------------------------------------------------------
/docs/src/router.md:
--------------------------------------------------------------------------------
1 | One line of code and one route configuration file, realize the function of `react-router`, and match the transition animation effect of native route.
2 |
3 | 一行代码 + 一个路由配置文件,就可以实现 `react-router` 的功能,并让你的 Web 页面切换媲美 Native 路由的转场动画体验
4 |
5 | ### 一、Brief Introduction 概述
6 |
7 | > [react-router-virgo](https://github.com/JackXJR/react-router-virgo) is a router with secondary encapsulation based on `react-router`, which makes the integrated routing function extremely simple. In addition, the extended functions such as route transition animation are added: `No transition animation`, `push transition animation`, `present transition animation`
8 | >
9 | > [react-router-virgo](https://github.com/JackXJR/react-router-virgo)是在 react-router 的基础上进行二次封装的 Router,使集成路由功能变得极其简单。此外,还增加了路由转场动画等扩展功能:`无转场动画`, `Push 转场动画`, `Present 转场动画`
10 |
11 | - 无转场动画
12 |
13 | 
14 |
15 | - Push 转场动画
16 |
17 | 
18 |
19 | - Present 转场动画
20 |
21 | 
22 |
23 | ### 二、Installation 安装
24 |
25 | ```
26 | ### use npm
27 | ### 使用npm
28 | npm install --save react-router-virgo
29 |
30 | ### use yarn
31 | ### 使用yarn
32 | yarn add react-router-virgo
33 | ```
34 |
35 | ### 三、RouterConfig 设置路由配置文件
36 |
37 | > Set route profile `RouterConfig.js`
38 | >
39 | > 设置路由配置文件 `RouterConfig.js`
40 |
41 | #### 3.1 Example 代码演示
42 |
43 | ```javascript
44 | import { Home, Detail } from './pages/index'
45 |
46 | // Three scenes are exemplified: no animation, push transition animation and present transition animation
47 | // 例举了无动画、push转场动画、present转场动画三种场景
48 | export const RouterConfig = [
49 | { path: '/', component: Home },
50 | // No transition animation (the new page directly covers the current page)
51 | // 无转场动画(新页面直接覆盖当前页面)
52 | {
53 | path: '/detail',
54 | component: Detail,
55 | sceneConfig: {
56 | enter: 'no-animation',
57 | exit: 'no-animation',
58 | },
59 | },
60 | // Push transition animation (when opening the page, overwrite from left to right; when closing the page, withdraw from right to left)
61 | // Push 转场动画(打开页面时,从左往右覆盖;关闭页面时,从右往左收回)
62 | {
63 | path: '/push/detail',
64 | component: Detail,
65 | sceneConfig: {
66 | enter: 'from-right',
67 | exit: 'to-right',
68 | },
69 | },
70 | // Present transition animation (when opening the page, it will pop up from the bottom; when closing the page, it will pop up from the top)
71 | // Present 转场动画(打开页面时,从下往上弹起;关闭页面时,从上往下收起)
72 | {
73 | path: '/present/detail',
74 | component: Detail,
75 | sceneConfig: {
76 | enter: 'from-bottom',
77 | exit: 'to-bottom',
78 | },
79 | },
80 | ]
81 | ```
82 |
83 | #### 3.2 RouterConfig Description 配置项说明
84 |
85 | | key | description | type | default | |
86 | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------- | ---------- |
87 | | `path` | The routing path can take parameters. The one after `/:` is a parameter, such as `/ detail /: id`. The parameter is id. 路由路径,可以带参数,在`/:`后的为参数,如 `/detail/:id`, 参数为 id | string | | `Required` |
88 | | `component` | Page components of route path mapping. 路由路径映射的页面组件 | class | | `Required` |
89 | | `sceneConfig` | The route transition animation configuration supports three scenes: `no animation`, `push animation` and `present animation`. The default is push animation. 路由转场动画配置,支持`无动画`、`Push 动画`、`Present 动画`三种场景,默认使用 Push 动画 | object | `{enter: 'from-right', exit: 'to-right'}` | `Optional` |
90 | | `exact` | Use exact match or not. 是否使用精准匹配 | bool | true | `Optional` |
91 |
92 | - Routing transition animation parameter `sceneConfig` configuration supports the following three scenarios
93 | - 路由转场动画参数`sceneConfig`配置,支持以下三种场景
94 |
95 | ```
96 | ### No animation configuration
97 | ### 无动画配置
98 | {
99 | enter: 'no-animation',
100 | exit: 'no-animation',
101 | }
102 |
103 | ### Push animation configuration
104 | ### Push动画配置
105 | {
106 | enter: 'from-right',
107 | exit: 'to-right',
108 | }
109 |
110 | ### Present animation configuration
111 | ### Present动画配置
112 | {
113 | enter: 'from-bottom',
114 | exit: 'to-bottom',
115 | }
116 | ```
117 |
118 | ### 四、Use Router 使用路由
119 |
120 | > Add `Router` to entry file `App.js`
121 | >
122 | > 在入口文件`App.js`中添加`Router`
123 |
124 | #### 4.1 Example 代码演示
125 |
126 | ```javascript
127 | import React from 'react'
128 | import Router from './router/Router'
129 | import { RouterConfig } from './RouterConfig'
130 | import './index.css'
131 |
132 | function App() {
133 | return
134 | }
135 |
136 | export default App
137 | ```
138 |
139 | #### 4.2 API
140 |
141 | | props | description | type | default | |
142 | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----- | ------- | ---------- |
143 | | `routerConfig` | Route configuration data. 路由配置数据 | array | [] | `Required` |
144 | | `useBrowserRouter` | Routing type `BrowserRouter/HashRouter`. By default, HashRouter is used. 路由类型 `BrowserRouter/HashRouter`,默认使用 `HashRouter` | bool | false | `Optional` |
145 | | `useAnimatedSwitch` | Use transition animation or not. 是否使用转场过渡动画 | bool | true | `Optional` |
146 |
147 | ### 五. FAQ 常见问题
148 |
149 | > Q: Which routing types are supported?
150 | > 支持哪些路由类型?
151 |
152 | - Two types of `BrowserRouter` and `HashRouter` are supported. They can be set by the property `useBrowserRouter`. The default is HashRouter
153 | - 目前支持 BrowserRouter 和 HashRouter 两种类型,可通过属性`useBrowserRouter`来设置,默认使用 HashRouter
154 |
155 | > Q: What transition animations are supported?
156 | > 支持哪些转场动画?
157 |
158 | - It supports three kinds of scenes: no animation, pop-up presentation animation, and right to left push animation. You can configure `sceneConfig` according to rules in the routing configuration file. If the sceneConfig field is not configured, push animation is used by default
159 | - 目前支持无动画、从下往上弹起的 Present 动画,从右往左打开的 Push 动画三种场景。可以在路由配置文件中按规则配置`sceneConfig`即可,如果未配置 sceneConfig 字段,则默认使用 Push 动画
160 |
161 | > Q: After opening a new page, will the previous page be destroyed?
162 | > 打开新页面后,上一级页面是否会被销毁?
163 |
164 | - It will be destroyed. When returning to the previous page, the page will be re rendered, and the subsequent version iterations will support the stack routing function.
165 | - 会被销毁,返回上一级页面时,页面会重新渲染,后续版本迭代会支持 Stack 路由功能。
166 |
167 | > Q:Why does compilation fail after installation?
168 | > 安装后,编译失败的原因?
169 |
170 | - Confirm whether there are two dependencies `react-router-dom` and `react-transition-group` in your project. If not, please import the dependency through yarn or NPM.
171 | - 确认下项目里是否有 `react-router-dom`、`react-transition-group` 这 2 个依赖,如果没有,请通过 yarn 或者 npm 引入依赖.
172 |
173 | ```
174 | yarn add react-router-dom react-transition-group
175 | ```
176 |
177 | > Q: Whether the project developed by TS is supported
178 | > 是否支持 ts 开发的项目
179 |
180 | - Subsequent iterations will support
181 | - 后续迭代会支持
182 |
183 | ### 六. More 其它
184 |
185 | If you are interested in more specific information, go to the code. If you find a bug, please mention an [issue](https://github.com/JackXJR/react-router-virgo/issues), I will repair and optimize it as soon as possible...
186 |
187 | 更具体的信息大家感兴趣的话去看代码吧,如果发现 bug,请提一个[issue](https://github.com/JackXJR/react-router-virgo/issues),我会第一时间进行修复和优化...
188 |
189 | > Welcome to use, if you think it's good, please give a little `star` encouragement~
190 | >
191 | > 欢迎使用,觉得不错请给一个小小的 `star` 鼓励一下~
192 |
--------------------------------------------------------------------------------
/docs/src/router_how.md:
--------------------------------------------------------------------------------
1 | 在使用 React 开发 web 页面的时候,一般都会使用`react-router`来实现路由功能,相较于 native 路由流畅丝滑的体验,web 页面切换起来会很生硬。作为从 iOS 转前端的我来说,尤其不能接受,因此,在 react-router 的基础上,封装了一个 Router 库 [react-router-virgo](https://github.com/JackXJR/react-router-virgo),让使用者只需要`一行代码 + 一个路由配置文件`就可以使 Web 页面切换能达到 native 路由切换的流畅体验。
2 |
3 | ### 一、react-router-virgo 简介
4 |
5 | 这个路由组件还没完全达到我的预期,后面有时间会继续迭代优化。目前这个路由组件支持以下功能:
6 |
7 | - 无转场动画切换(现有的 web 页面切换体验)
8 | - push 动画切换(右侧淡入,右侧淡出的 Native 路由切换体验)
9 | - present 动画切换(下方淡入,下方淡出的 Native 路由模态切换体验)
10 | - 支持 HashRouter,BrowserRouter 两种路由
11 |
12 | > 总体上,基本能达到 native 路由的切换体验,当然,毕竟是 web 页面,相对于 iOS 的原生的 native 路由体验还是有点差距,感兴趣的也可以 [运行完整 demo](https://github.com/JackXJR/react-router-virgo)体验下。。。
13 |
14 | 下面我们来简要看一下实现原理吧
15 |
16 | 简单来说,`react-router-virgo`是在 [react-router](https://reacttraining.com/react-router/web/guides/quick-start) 和 [react-transition-group](https://reactcommunity.org/react-transition-group/#Transition-prop-mountOnEnter) 的基础上进行二次封装的 Router。这里也会分这两部分进行递进分析
17 |
18 | ### 二、react-router
19 |
20 | 首先,我们先简要介绍下 react-router 的基本用法(详细看[官网](https://reacttraining.com/react-router/web/guides/quick-start)介绍)。
21 |
22 | 我们主要使用到 react-router 提供的 HashRouter/BrowserRouter,Switch,Route 三个组件。
23 |
24 | #### 2.1 HashRouter/BrowserRouter
25 |
26 | react-router 主要提供了两种 Router
27 |
28 | - HashRouter: hash 形式实现的路由,使用 `createHashHistory` 创建的 history
29 |
30 | - BrowserRouter:以 html5 提供的 history api 形式实现的路由,使用 `createBrowserHistory` 创建的 history
31 |
32 | #### 2.2 Route
33 |
34 | 路由组件,`path` 指定匹配的路由,`component` 指定路由匹配时展示的组件(Route 也可以通过 children 和 render 的形式创建展示的组件)。
35 |
36 | ```javascript
37 |
38 | ```
39 |
40 | #### 2.3 Switch
41 |
42 | 多个 Route 组件同时匹配时,默认都会显示,但是被 Switch 包裹起来的 Route 组件只会显示第一个被匹配上的路由。路由的转场动画,其实就是基于 Switch 进行封装,使页面切换时,具有动画效果。
43 |
44 | #### 2.4 代码演示
45 |
46 | > 使用`react-router-dom`中的 `HashRouter/BrowserRouter` 、 `Route`、`Switch` 一个简单的无动画路由切换功能,具体如下:
47 |
48 | ```javascript
49 | import React from 'react';
50 | import { HashRouter, Route, Switch } from 'react-router-dom';
51 | import { RouterConfig } from './routerConfig';
52 |
53 | const Router = () => (
54 | // 这里使用 HashRouter , 也可以使用 BrowserRouter
55 |
56 |
57 | {RouterConfig.map((config, index) => {
58 | return ;
59 | })}
60 |
61 |
62 | );
63 |
64 | export default Router;
65 | ```
66 |
67 | 那怎么让页面切换的时候,带有转场动画效果呢,这就需要结合 react-transition-group 了
68 |
69 | ### 三、react-transition-group
70 |
71 | 在介绍实现转场动画之前,我们得先学习如何使用 [react-transition-group](https://reactcommunity.org/react-transition-group/#Transition-prop-mountOnEnter)。基于此,接下来就将对其提供的 `CSSTransition` 和 `TransitionGroup` 这两个组件展开简要介绍。
72 |
73 | #### 3.1 CSSTransition
74 |
75 | CSSTransition 会自动给包裹的标签添加样式,是一个可以通过设置 css 来实现的过渡动画组件。我们先来看下 CSSTransition 的属性:
76 |
77 | | 属性 | 详情 |
78 | | --------- | ------------------------------------------------------------- |
79 | | `in` | 取值 `true/false`,决定包裹的元素是要进行出场动画还是入场动画 |
80 | | `timeout` | 设置动画时间 |
81 |
82 | 实际上,CSSTransition 并没有给组件任何动画效果,只是在一段时间内,给包裹的组件加上三个类,这个三类中,我们可以写动画效果。
83 |
84 | - in 属性的值从 false 变为 true,就会元素加上下面三个类(这里前缀`xxx` 代指设置的 css 类型,默认是`fade`),即执行入场动画:
85 |
86 | | css 类 | 详情 |
87 | | ------------------ | ---------------------------------------------------------------------------------- |
88 | | `xxx-enter` | 入场动画的第一个瞬间(帧)加入的,动画即将结束前消失 |
89 | | `xxx-enter-active` | `xxx-enter` 加入后,第二个瞬间加入,持续到入场动画即将执行完成,动画即将结束前消失 |
90 | | `xxx-enter-done` | 入场动画结束瞬间,加入之后一直存在 |
91 |
92 | - 相反地,当 in 属性置为 false 时,CSSTransition 会给子组件加上 `xxx-exit`、`xxx-exit-active`、`xxx-exit-done` 的 class。(更多详细介绍可以戳[官网](http://reactcommunity.org/react-transition-group/css-transition)查看)
93 |
94 | 基于以上两点,我们给打开页面时的 class 设置为`forward-from-right`,则对应的 css 样式:
95 |
96 | ```javascript
97 | /**
98 | * 打开页面:右侧淡入,右侧淡出
99 | */
100 | .forward-from-right-enter {
101 | z-index: 2;
102 | transform: translate3d(100%, 0, 0);
103 | }
104 |
105 | .forward-from-right-enter-active {
106 | z-index: 2;
107 | transform: translate3d(0, 0, 0);
108 | transition: all 300ms;
109 | }
110 |
111 | .forward-from-right-enter-done {
112 | z-index: 2;
113 | }
114 |
115 | .forward-from-right-exit {
116 | z-index: 1;
117 | transform: translate3d(0, 0, 0);
118 | }
119 |
120 | .forward-from-right-exit-active {
121 | z-index: 1;
122 | transform: translate3d(-30%, 0, 0);
123 | transition: all 300ms;
124 | }
125 |
126 | .forward-from-right-exit-done {
127 | z-index: 1;
128 | }
129 | ```
130 |
131 | #### 3.2 TransitionGroup
132 |
133 | 用 CSSTransition 可以实现单个页面的动画,而路由的转场动画需要管理新旧两个页面的联动切换。如何让新旧页面切换的过程中,同时存在两个 DOM 节点,切换结束后,再移除旧节点呢?
134 |
135 | 为此我们再来介绍 react-transition-group 提供的[TransitionGroup](https://reactcommunity.org/react-transition-group/transition-group)这个组件。
136 |
137 | > 如[官网](https://reactcommunity.org/react-transition-group/transition-group)介绍,TransitionGroup 组件就是用来管理一堆节点 mounting 和 unmounting 过程的组件,非常适合处理路由切换的情况。TransitionGroup 在感知到其 children 变化时,会先保存住即将要被移除的节点,而在其动画结束时才会真正移除该节点。
138 |
139 | #### 3.2 代码演示
140 |
141 | > 使用 `react-transition-group`中的`TransitionGroup`和`CSSTransition` 对 Switch 进行封装,实现路由转场动画组件`AnimatedSwitch`
142 |
143 | ```javascript
144 | import React from 'react';
145 | import { Switch, withRouter } from 'react-router-dom';
146 | import { CSSTransition, TransitionGroup } from 'react-transition-group';
147 | import './AnimatedSwitch.css'; // 动画样式
148 |
149 | let oldLocation = null;
150 | class AnimatedSwitch extends React.Component {
151 | static propsTypes = {
152 | routerConfig: PropsTypes.array.isRequired,
153 | };
154 | render() {
155 | const { location, history, children, routerConfig } = this.props;
156 | let classNames = '';
157 | if (history.action === 'PUSH') {
158 | // 打开页面的转场动画
159 | classNames = 'forward-from-right';
160 | } else if (history.action === 'POP' && oldLocation) {
161 | // 关闭页面的转场动画
162 | classNames = 'back-to-right';
163 | }
164 | oldLocation = location;
165 | // 使用 TransitionGroup 和 CSSTransition 包裹 Switch,实现转场动画
166 | return (
167 | React.cloneElement(child, { classNames })}
170 | >
171 |
172 | {children}
173 |
174 |
175 | );
176 | }
177 | }
178 | // 通过 withRouter 包裹,可以从props中获取location,history等对象。
179 | export default withRouter(AnimatedSwitch);
180 | ```
181 |
182 | 至此,路由转场动画算是基本实现了。
183 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-app",
3 | "version": "0.1.1",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.13.1",
7 | "react-dom": "^16.13.1",
8 | "react-router-dom": "^5.2.0",
9 | "react-router-virgo": "^0.1.10",
10 | "react-transition-group": "^4.4.1"
11 | },
12 | "devDependencies": {
13 | "react-scripts": "3.4.1"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject",
20 | "start-doc": "npx docsify serve ./docs"
21 | },
22 | "eslintConfig": {
23 | "extends": "react-app"
24 | },
25 | "browserslist": {
26 | "production": [
27 | ">0.2%",
28 | "not dead",
29 | "not op_mini all"
30 | ],
31 | "development": [
32 | "last 1 chrome version",
33 | "last 1 firefox version",
34 | "last 1 safari version"
35 | ]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
15 |
16 |
17 | React App
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackXJR/react-router-virgo/95a9b62a1d8de9dd67187f40a37a5c4829442854/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | // import { Router } from 'react-router-virgo'
3 | import Router from './router/Router'
4 | import { RouterConfig } from './RouterConfig'
5 | import './index.css'
6 |
7 | function App() {
8 | return
9 | }
10 |
11 | export default App
12 |
--------------------------------------------------------------------------------
/src/RouterConfig.js:
--------------------------------------------------------------------------------
1 | // import { SceneConfig } from 'react-router-virgo'
2 | import { SceneConfig } from './router/Router'
3 | import { Home, Detail, PopPage, ReplacePage, HybridPage } from './pages/index'
4 |
5 | // 无转场动画(新页面直接覆盖当前页面)
6 | // 属性 useAnimatedSwitch 设置为false时,sceneConfig 可不配置
7 | const NoAnimateConfig = [
8 | {
9 | path: '/detail/:type',
10 | component: Detail,
11 | sceneConfig: SceneConfig.NONE,
12 | },
13 | {
14 | path: '/pop-page',
15 | component: PopPage,
16 | sceneConfig: SceneConfig.NONE,
17 | },
18 | ]
19 |
20 | // Push 转场动画(打开时,从左往右覆盖;关闭时,从右往左收回)
21 | // 属性 useAnimatedSwitch 未设置,或设置为true时,sceneConfig 可不配置
22 | const PushConfig = [
23 | {
24 | path: '/push/detail/:type',
25 | component: Detail,
26 | sceneConfig: SceneConfig.PUSH,
27 | },
28 | {
29 | path: '/push/pop-page',
30 | component: PopPage,
31 | sceneConfig: SceneConfig.PUSH,
32 | },
33 | {
34 | path: '/push/replace-page',
35 | component: ReplacePage,
36 | sceneConfig: SceneConfig.PUSH,
37 | },
38 | {
39 | path: '/push/hybrid-page',
40 | component: HybridPage,
41 | sceneConfig: SceneConfig.PUSH,
42 | },
43 | ]
44 |
45 | // Present 转场动画(打开时,从下往上弹起;关闭时,从上往下收起)
46 | // sceneConfig 必须设置
47 | const PresentConfig = [
48 | {
49 | path: '/present/detail/:type',
50 | component: Detail,
51 | sceneConfig: SceneConfig.PRESENT,
52 | },
53 | {
54 | path: '/present/pop-page',
55 | component: PopPage,
56 | sceneConfig: SceneConfig.PRESENT,
57 | },
58 | ]
59 |
60 | /**
61 | * 路由配置文件(NoAnimateConfig、PushConfig、PresentConfig)
62 | *
63 | * path 路由路径,路径可以带参数,在:后的即为参数,如 '/detail/:id', 参数为id。必填
64 | * component 路径对应的组件页面。必填
65 | * sceneConfig 转场动画配置,支持无动画、push动画、present动画三种场景。选填
66 | * exact 是否使用精准匹配,默认为true。选填
67 | */
68 | export const RouterConfig = [{ path: '/', component: Home }]
69 | .concat(NoAnimateConfig)
70 | .concat(PushConfig)
71 | .concat(PresentConfig)
72 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 | import * as serviceWorker from './serviceWorker'
5 |
6 | ReactDOM.render( , document.getElementById('root'))
7 |
8 | serviceWorker.unregister()
9 |
--------------------------------------------------------------------------------
/src/pages/Detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button, Title } from './components'
3 |
4 | const PageType = {
5 | PUSH: 'PUSH',
6 | PRESENT: 'PRESENT',
7 | NONE: 'NONE',
8 | }
9 |
10 | class Detail extends React.Component {
11 | constructor(props) {
12 | super(props)
13 |
14 | let curPath =
15 | (this.props.history.location && this.props.history.location.pathname) ||
16 | ''
17 | // 默认是无动画页面
18 | this.pageType = PageType.NONE
19 | if (curPath.indexOf('/push/') === 0) {
20 | // Push 动画页面
21 | this.pageType = PageType.PUSH
22 | } else if (curPath.indexOf('/present/') === 0) {
23 | // Present 动画页面
24 | this.pageType = PageType.PRESENT
25 | }
26 | }
27 |
28 | goBack() {
29 | this.props.history.goBack()
30 | }
31 |
32 | // 根据不同页面类型展示标题
33 | getTitle() {
34 | if (this.pageType === PageType.PUSH) {
35 | return '测试Push动画场景'
36 | } else if (this.pageType === PageType.PRESENT) {
37 | return '测试Present动画场景'
38 | } else {
39 | return '测试无动画场景'
40 | }
41 | }
42 |
43 | // 打开新页面
44 | pushPage(path) {
45 | if (this.pageType === PageType.PUSH) {
46 | // Push 动画
47 | this.props.history.push('/push' + path)
48 | } else if (this.pageType === PageType.PRESENT) {
49 | // Present 动画
50 | this.props.history.push('/present' + path)
51 | } else {
52 | // 无动画
53 | this.props.history.push(path)
54 | }
55 | }
56 |
57 | render() {
58 | return (
59 |
60 |
61 | this.goBack()} />
62 | this.pushPage('/pop-page')}
65 | />
66 | window.history.pushState({}, '', '/#/pop-page')}
69 | />
70 |
71 | )
72 | }
73 | }
74 |
75 | export default Detail
76 |
77 | const styles = {
78 | container: {
79 | display: 'flex',
80 | backgroundColor: 'rgb(97,218,251)',
81 | height: '100vh',
82 | width: '100vw',
83 | overflow: 'hidden',
84 | flexDirection: 'column',
85 | },
86 | button: { marginTop: 10 },
87 | }
88 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button, Title } from './components'
3 |
4 | class Home extends React.Component {
5 | gotoPage(pathname, query) {
6 | this.props.history.push({ pathname, query })
7 | }
8 |
9 | render() {
10 | return (
11 |
12 |
13 | this.gotoPage('/detail/无动画')}
16 | />
17 | {
20 | this.gotoPage('/push/detail/Push动画')
21 | }}
22 | />
23 | {
26 | this.gotoPage('/present/detail/Present动画')
27 | }}
28 | />
29 | this.gotoPage('/push/hybrid-page')}
32 | />
33 |
34 | )
35 | }
36 | }
37 |
38 | export default Home
39 |
40 | const styles = {
41 | container: {
42 | display: 'flex',
43 | height: '100vh',
44 | width: '100vw',
45 | overflow: 'hidden',
46 | flexDirection: 'column',
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/HybridPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button, Title } from './components'
3 |
4 | class HybridPage extends React.Component {
5 | goBack() {
6 | this.props.history.goBack()
7 | }
8 | /**
9 | * 可以给某一个页面同时配置多种动画,以 PopPage 为例
10 | *
11 | * 1.打开配置路径 "/pop-page" 时,无动画
12 | * 2.打开配置路径 "/push/pop-page" 时,Push动画
13 | * 3.打开配置路径 "/present/pop-page" 时,Present动画
14 | */
15 | render() {
16 | return (
17 |
18 |
19 | this.goBack()} />
20 | this.props.history.push('/pop-page')}
23 | />
24 | this.props.history.push('/push/pop-page')}
27 | />
28 | this.props.history.push('/present/pop-page')}
31 | />
32 |
33 | )
34 | }
35 | }
36 |
37 | export default HybridPage
38 |
39 | const styles = {
40 | container: {
41 | display: 'flex',
42 | backgroundColor: 'orange',
43 | height: '100vh',
44 | width: '100vw',
45 | overflow: 'hidden',
46 | flexDirection: 'column',
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/PopPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button, Title } from './components'
3 |
4 | class PopPage extends React.Component {
5 | pushPage(pathname, query) {
6 | this.props.history.push({ pathname, query })
7 | }
8 |
9 | replacePage(pathname, query) {
10 | this.props.history.replace({ pathname, query })
11 | }
12 |
13 | goBack() {
14 | this.props.history.goBack()
15 | }
16 |
17 | go(num) {
18 | this.props.history.go(num)
19 | }
20 |
21 | href(pathname) {
22 | window.location.href = pathname
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
29 | this.goBack()} />
30 | this.go(-1)} />
31 | this.go(-2)} />
32 | this.href('/')} />
33 |
34 | )
35 | }
36 | }
37 |
38 | export default PopPage
39 |
40 | const styles = {
41 | container: {
42 | display: 'flex',
43 | backgroundColor: 'yellow',
44 | height: '100vh',
45 | width: '100vw',
46 | overflow: 'hidden',
47 | flexDirection: 'column',
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/src/pages/ReplacePage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Title, Toast } from './components';
3 |
4 | export default class ReplacePage extends React.Component {
5 | goBack() {
6 | this.props.history.goBack();
7 | }
8 |
9 | render() {
10 | return (
11 |
12 |
13 | this.goBack()} />
14 | this.props.history.replace('/push/pop-page')}
17 | />
18 | window.location.replace('/push/pop-page')}
21 | />
22 |
25 | window.history.replaceState({}, '', '/#/push/pop-page')
26 | }
27 | />
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | const styles = {
35 | container: {
36 | display: 'flex',
37 | backgroundColor: 'rgb(97,218,251)',
38 | height: '100vh',
39 | width: '100vw',
40 | overflow: 'hidden',
41 | flexDirection: 'column',
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/src/pages/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class Button extends React.Component {
4 | render() {
5 | const { style = {}, title, onClick } = this.props
6 | let btnStyle = Object.assign({}, styles.button, style)
7 | return (
8 | {
11 | onClick && onClick()
12 | }}
13 | >
14 | {title}
15 |
16 | )
17 | }
18 | }
19 |
20 | const styles = {
21 | button: {
22 | margin: 15,
23 | backgroundColor: 'rgb(20,120,227)',
24 | padding: 15,
25 | color: 'white',
26 | borderRadius: 5,
27 | fontWeight: '500',
28 | fontSize: 18,
29 | textAlign: 'center',
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/src/pages/components/Title.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class Title extends React.Component {
4 | render() {
5 | const { title, style } = this.props
6 | const titleStyle = Object.assign({}, styles.title, style)
7 | return {title}
8 | }
9 | }
10 |
11 | const styles = {
12 | title: {
13 | fontSize: 25,
14 | fontWeight: 'bold',
15 | padding: 20,
16 | textAlign: 'center',
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/components/Toast.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Toast extends React.Component {
4 | constructor(props) {
5 | super(props);
6 |
7 | this.state = {
8 | showToast: true,
9 | };
10 | }
11 |
12 | componentDidMount() {
13 | setTimeout(() => {
14 | this.setState({ showToast: false });
15 | }, 1000);
16 | }
17 |
18 | render() {
19 | const { title = '' } = this.props;
20 | if (!this.state.showToast) return null;
21 | return (
22 |
25 | );
26 | }
27 | }
28 |
29 | const styles = {
30 | container: {
31 | position: 'absolute',
32 | width: '100vw',
33 | height: '100vh',
34 | display: 'flex',
35 | alignItems: 'center',
36 | justifyContent: 'center',
37 | },
38 | title: {
39 | display: 'flex',
40 | color: '#FFFFFF',
41 | fontSize: 14,
42 | maxWidth: '80vw',
43 | backgroundColor: '#414141',
44 | borderRadius: 4,
45 | padding: 10,
46 | marginTop: -60,
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/src/pages/components/index.js:
--------------------------------------------------------------------------------
1 | import Button from './Button'
2 | import Title from './Title'
3 | import Toast from './Toast'
4 |
5 | export { Button, Title, Toast }
6 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import Home from './Home';
2 | import Detail from './Detail';
3 | import PopPage from './PopPage';
4 | import ReplacePage from './ReplacePage';
5 | import HybridPage from './HybridPage';
6 |
7 | export { Home, Detail, ReplacePage, HybridPage, PopPage };
8 |
--------------------------------------------------------------------------------
/src/router/AnimatedSwitch.css:
--------------------------------------------------------------------------------
1 | .router-wrapper {
2 | overflow: hidden;
3 | }
4 |
5 | /**
6 | * No animation transition
7 | * 无动画转场切换
8 | */
9 | .forward-no-animation-enter {
10 | opacity: 1;
11 | z-index: 2;
12 | }
13 |
14 | .forward-no-animation-enter-active {
15 | opacity: 1;
16 | z-index: 2;
17 | }
18 |
19 | .forward-no-animation-enter-done {
20 | z-index: 2;
21 | }
22 |
23 | .forward-no-animation-exit {
24 | opacity: 0;
25 | z-index: 1;
26 | }
27 |
28 | .forward-no-animation-exit-active {
29 | opacity: 0;
30 | z-index: 1;
31 | }
32 |
33 | .forward-no-animation-exit-done {
34 | z-index: 1;
35 | }
36 |
37 | .back-no-animation-enter {
38 | opacity: 1;
39 | z-index: 2;
40 | }
41 |
42 | .back-no-animation-enter-active {
43 | opacity: 1;
44 | z-index: 2;
45 | }
46 |
47 | .back-no-animation-exit {
48 | opacity: 0;
49 | z-index: 1;
50 | }
51 |
52 | .back-no-animation-exit-active {
53 | opacity: 0;
54 | z-index: 1;
55 | }
56 |
57 | /**
58 | * Fade in right, fade out right
59 | * 右侧淡入,右侧淡出
60 | */
61 | .forward-from-right-enter {
62 | z-index: 2;
63 | transform: translate3d(100%, 0, 0);
64 | }
65 |
66 | .forward-from-right-enter-active {
67 | z-index: 2;
68 | transform: translate3d(0, 0, 0);
69 | transition: all 300ms;
70 | }
71 |
72 | .forward-from-right-enter-done {
73 | z-index: 2;
74 | }
75 |
76 | .forward-from-right-exit {
77 | z-index: 1;
78 | transform: translate3d(0, 0, 0);
79 | }
80 |
81 | .forward-from-right-exit-active {
82 | z-index: 1;
83 | transform: translate3d(-30%, 0, 0);
84 | transition: all 300ms;
85 | }
86 |
87 | .forward-from-right-exit-done {
88 | z-index: 1;
89 | }
90 |
91 | .back-to-right-enter {
92 | z-index: 1;
93 | transform: translate3d(-30%, 0, 0);
94 | }
95 |
96 | .back-to-right-enter-active {
97 | z-index: 1;
98 | transform: translate3d(0%, 0, 0);
99 | transition: all 300ms;
100 | }
101 |
102 | .back-to-right-enter-done {
103 | z-index: 1;
104 | }
105 |
106 | .back-to-right-exit {
107 | z-index: 2;
108 | transform: translate3d(0%, 0, 0);
109 | }
110 |
111 | .back-to-right-exit-active {
112 | z-index: 2;
113 | transform: translate3d(100%, 0, 0);
114 | transition: all 300ms;
115 | }
116 |
117 | .back-to-right-exit-done {
118 | z-index: 2;
119 | }
120 |
121 | /**
122 | * Fade in below, fade out below
123 | * 下方淡入,下方淡出
124 | */
125 | .forward-from-bottom-enter {
126 | z-index: 2;
127 | opacity: 0;
128 | transform: translateY(100%);
129 | }
130 |
131 | .forward-from-bottom-enter-active {
132 | z-index: 2;
133 | opacity: 1;
134 | transform: translateY(0);
135 | transition: all 300ms;
136 | }
137 |
138 | .forward-from-bottom-exit {
139 | z-index: 1;
140 | opacity: 1;
141 | }
142 |
143 | .forward-from-bottom-exit-active {
144 | z-index: 1;
145 | opacity: 0.3;
146 | transition: all 300ms;
147 | }
148 |
149 | .back-to-bottom-enter {
150 | z-index: 1;
151 | opacity: 0.3;
152 | }
153 |
154 | .back-to-bottom-enter-active {
155 | z-index: 1;
156 | opacity: 1;
157 | transition: all 300ms;
158 | }
159 |
160 | .back-to-bottom-exit {
161 | z-index: 2;
162 | opacity: 1;
163 | transform: translateY(0);
164 | }
165 |
166 | .back-to-bottom-exit-active {
167 | z-index: 2;
168 | opacity: 0;
169 | transform: translateY(100%);
170 | transition: all 300ms;
171 | }
172 |
--------------------------------------------------------------------------------
/src/router/AnimatedSwitch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, withRouter } from 'react-router-dom';
3 | import { CSSTransition, TransitionGroup } from 'react-transition-group';
4 | import './AnimatedSwitch.css';
5 |
6 | const PropsTypes = require('prop-types');
7 |
8 | const DEFAULT_SCENE_CONFIG = {
9 | enter: 'from-right',
10 | exit: 'to-right',
11 | };
12 |
13 | // Get the configuration of transition animation
14 | // 获取转场动画配置
15 | const getSceneConfig = (location, routerConfig = []) => {
16 | const matchedRoute = routerConfig.find((config) => {
17 | let paramIndex = config.path.indexOf('/:');
18 | if (paramIndex === -1) {
19 | return config.path === location.pathname;
20 | } else {
21 | let configPath = config.path.slice(paramIndex).split('/:'),
22 | locationPath = location.pathname.slice(paramIndex).split('/');
23 | return (
24 | location.pathname.indexOf(config.path.slice(0, paramIndex)) === 0 &&
25 | configPath.length === locationPath.length
26 | );
27 | }
28 | });
29 | return (matchedRoute && matchedRoute.sceneConfig) || DEFAULT_SCENE_CONFIG;
30 | };
31 |
32 | let oldLocation = null;
33 | class AnimatedSwitch extends React.Component {
34 | static propsTypes = {
35 | routerConfig: PropsTypes.array.isRequired,
36 | };
37 |
38 | render() {
39 | const { location, history, children, routerConfig } = this.props;
40 | let classNames = '';
41 | if (history.action === 'PUSH') {
42 | // When opening the page, the animation configuration is obtained from the configuration. By default, the push animation opened from right to left is used
43 | // 打开页面时的动画,从配置项中获取动画配置,默认使用从右至左打开的PUSH动画
44 | classNames = 'forward-' + getSceneConfig(location, routerConfig).enter;
45 | } else if (history.action === 'POP' && oldLocation) {
46 | // The animation when closing the page, get the animation configuration from the configuration, and use the left to right closed push animation by default
47 | // 关闭页面时的动画,从配置项中获取动画配置,默认使用从左至右关闭的push动画
48 | classNames = 'back-' + getSceneConfig(oldLocation, routerConfig).exit;
49 | }
50 |
51 | // Update old location
52 | // 更新旧location
53 | oldLocation = location;
54 |
55 | // Set transition animation duration
56 | // 设置转场动画时长
57 | let useAnimation = classNames.indexOf('no-animation') === -1;
58 | return (
59 | React.cloneElement(child, { classNames })}
62 | >
63 |
64 | {children}
65 |
66 |
67 | );
68 | }
69 | }
70 |
71 | export default withRouter(AnimatedSwitch);
72 |
--------------------------------------------------------------------------------
/src/router/Router.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { HashRouter, BrowserRouter, Route, Switch } from 'react-router-dom'
3 | import AnimatedSwitch from './AnimatedSwitch'
4 | import StackSwitch from './StackSwitch'
5 | import { WrappedAnimatedRoute } from './WrapedComp'
6 |
7 | const PropTypes = require('prop-types')
8 |
9 | // Transition animation type configuration
10 | // 转场动画类型配置
11 | export const SceneConfig = {
12 | // No animation configuration
13 | // 无动画配置
14 | NONE: {
15 | enter: 'no-animation',
16 | exit: 'no-animation',
17 | },
18 |
19 | // Push animation configuration
20 | // Push动画配置
21 | PUSH: {
22 | enter: 'from-right',
23 | exit: 'to-right',
24 | },
25 |
26 | // Present animation configuration
27 | // Present动画配置
28 | PRESENT: {
29 | enter: 'from-bottom',
30 | exit: 'to-bottom',
31 | },
32 | }
33 |
34 | class Router extends React.Component {
35 | static propTypes = {
36 | // By default, HashRouter is used. If 'usebrowserrouter' is set externally, BrowserRouter is used
37 | // 默认使用 HashRouter, 如果外部设置useBrowserRouter,则使用 BrowserRouter
38 | useBrowserRouter: PropTypes.bool,
39 | // Transition transition animation is used by default. If the external setting is false, no animation is used
40 | // 默认使用转场过渡动画,如果外部设置为false,则不使用动画
41 | useAnimatedSwitch: PropTypes.bool,
42 | // Similar to the native route effect, When you open or close a page, the previous page will not be destroyed
43 | // 完全类似native路由效果,打开和关闭页面时,均不销毁上一级页面
44 | useStackSwitch: PropTypes.bool,
45 | // Routing configuration table
46 | // 路由配置表
47 | routerConfig: PropTypes.array.isRequired,
48 | }
49 |
50 | render() {
51 | const {
52 | useBrowserRouter = false,
53 | useAnimatedSwitch = true,
54 | useStackSwitch = false,
55 | routerConfig = [],
56 | } = this.props
57 |
58 | // By default, HashRouter is used. If 'useBrowserRouter' is set externally, BrowserRouter is used
59 | // 默认使用 HashRouter, 如果外部设置useBrowserRouter,则使用 BrowserRouter
60 | const CommonRouter = useBrowserRouter ? BrowserRouter : HashRouter
61 |
62 | /**
63 | * If `useStackSwitch` is set, the native routing effect of the page is not destroyed
64 | * If `useStackSwitch` is not set and `useAnimatedSwitch` is set to false, the non animated routing effect is used
65 | * If `useStackSwitch` and `useAnimatedSwitch` are not set, the routing effect of the destroy page is used by default
66 |
67 | * 如果设置了useStackSwitch,则使用不销毁页面的native路由效果
68 | * 如果未设置useStackSwitch,且useAnimatedSwitch设置为false,则使用无动画的路由效果
69 | * 如果未设置useStackSwitch和useAnimatedSwitch,则默认使用销毁页面的路由效果
70 | * */
71 | const CommonSwitch = useStackSwitch
72 | ? StackSwitch
73 | : useAnimatedSwitch
74 | ? AnimatedSwitch
75 | : Switch
76 |
77 | return (
78 |
79 |
80 | {useStackSwitch
81 | ? null
82 | : routerConfig.map((config, index) => {
83 | return (
84 |
90 | )
91 | })}
92 |
93 |
94 | )
95 | }
96 | }
97 |
98 | export default Router
99 |
--------------------------------------------------------------------------------
/src/router/StackSwitch.css:
--------------------------------------------------------------------------------
1 | .forward-no-animation-hide {
2 | display: none;
3 | transform: translate(0%, 0%) translateZ(0px);
4 | }
5 |
6 | .forward-no-animation {
7 | display: block;
8 | transform: translate(0%, 0%) translateZ(0px);
9 | }
10 |
11 | .forward-no-animation-active {
12 | transform: translate(0%, 0%) translateZ(0px);
13 | }
14 |
15 | .back-no-animation-hide {
16 | display: none;
17 | transform: translate(0%, 0%) translateZ(0px);
18 | }
19 |
20 | .back-no-animation {
21 | display: block;
22 | transform: translate(0%, 0%) translateZ(0px);
23 | }
24 |
25 | .back-no-animation-active {
26 | transform: translate(100%, 0%) translateZ(0px);
27 | }
28 |
29 | /**
30 | * 无动画切换
31 | */
32 |
33 | /**
34 | * 右侧淡入,右侧淡出
35 | */
36 | .forward-from-right-hide {
37 | display: none;
38 | transform: translate(-50%, 0%) translateZ(0px);
39 | }
40 |
41 | @keyframes push-animate {
42 | from {
43 | transform: translate(0%, 0%) translateZ(0px);
44 | }
45 | to {
46 | transform: translate(-50%, 0%) translateZ(0px);
47 | }
48 | }
49 |
50 | .forward-from-right {
51 | display: block;
52 | transform: translate(-50%, 0%) translateZ(0px);
53 | animation: push-animate 0.4s;
54 | }
55 |
56 | @keyframes push-active-animate {
57 | from {
58 | transform: translate(100%, 0%) translateZ(0px);
59 | }
60 | to {
61 | transform: translate(0%, 0%) translateZ(0px);
62 | }
63 | }
64 |
65 | .forward-from-right-active {
66 | transform: translate(0%, 0%) translateZ(0px);
67 | animation: push-active-animate 0.4s;
68 | }
69 |
70 | .back-to-right-hide {
71 | display: none;
72 | transform: translate(-50%, 0%) translateZ(0px);
73 | }
74 |
75 | @keyframes pop-animate {
76 | from {
77 | transform: translate(-50%, 0%) translateZ(0px);
78 | }
79 | to {
80 | transform: translate(0%, 0%) translateZ(0px);
81 | }
82 | }
83 |
84 | .back-to-right {
85 | display: block;
86 | transform: translate(0%, 0%) translateZ(0px);
87 | animation: pop-animate 0.4s;
88 | }
89 |
90 | @keyframes pop-active-animate {
91 | from {
92 | transform: translate(0%, 0%) translateZ(0px);
93 | }
94 | to {
95 | transform: translate(100%, 0%) translateZ(0px);
96 | }
97 | }
98 | .back-to-right-active {
99 | transform: translate(100%, 0%) translateZ(0px);
100 | animation: pop-active-animate 0.4s;
101 | }
102 |
103 | /**
104 | * 下方淡入,下方淡出
105 | */
106 |
107 | .forward-from-bottom-hide {
108 | display: none;
109 | transform: translate(0%, -50%) translateZ(0px);
110 | }
111 |
112 | .forward-from-bottom {
113 | display: block;
114 | transform: translate(0%, 0%) translateZ(0px);
115 | }
116 |
117 | @keyframes push-bottom-active-animate {
118 | from {
119 | transform: translate(0%, 100%) translateZ(0px);
120 | }
121 | to {
122 | transform: translate(0%, 0%) translateZ(0px);
123 | }
124 | }
125 |
126 | .forward-from-bottom-active {
127 | transform: translate(0%, 0%) translateZ(0px);
128 | animation: push-bottom-active-animate 0.4s;
129 | }
130 |
131 | .back-to-bottom-hide {
132 | display: none;
133 | transform: translate(0%, -50%) translateZ(0px);
134 | }
135 |
136 | .back-to-bottom {
137 | display: block;
138 | transform: translate(0%, 0%) translateZ(0px);
139 | }
140 |
141 | @keyframes pop-bottom-active-animate {
142 | from {
143 | transform: translate(0%, 0%) translateZ(0px);
144 | }
145 | to {
146 | transform: translate(0%, 100%) translateZ(0px);
147 | }
148 | }
149 | .back-to-bottom-active {
150 | transform: translate(0%, 100%) translateZ(0px);
151 | animation: pop-bottom-active-animate 0.4s;
152 | }
153 |
--------------------------------------------------------------------------------
/src/router/StackSwitch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 | import { WrappedStackRoute } from './WrapedComp';
4 | import './StackSwitch.css';
5 |
6 | const PropsTypes = require('prop-types');
7 |
8 | const DEFAULT_SCENE_CONFIG = {
9 | enter: 'from-right',
10 | exit: 'to-right',
11 | };
12 |
13 | // 获取转场动画配置项
14 | const getSceneConfig = (location, routerConfig = []) => {
15 | const matchedRoute = routerConfig.find((config) => {
16 | let paramIndex = config.path.indexOf('/:');
17 | if (paramIndex === -1) {
18 | return config.path === location.pathname;
19 | } else {
20 | let configPath = config.path.slice(paramIndex).split('/:'),
21 | locationPath = location.pathname.slice(paramIndex).split('/');
22 | return (
23 | location.pathname.indexOf(config.path.slice(0, paramIndex)) === 0 &&
24 | configPath.length === locationPath.length
25 | );
26 | }
27 | });
28 | return (matchedRoute && matchedRoute.sceneConfig) || DEFAULT_SCENE_CONFIG;
29 | };
30 |
31 | // 获取location匹配的组件
32 | const getMatchedComponent = (location, routerConfig = []) => {
33 | const matchedRoute = routerConfig.find((config) => {
34 | let paramIndex = config.path.indexOf('/:');
35 | if (paramIndex === -1) {
36 | return config.path === location.pathname;
37 | } else {
38 | let configPath = config.path.slice(paramIndex).split('/:'),
39 | locationPath = location.pathname.slice(paramIndex).split('/');
40 | return (
41 | location.pathname.indexOf(config.path.slice(0, paramIndex)) === 0 &&
42 | configPath.length === locationPath.length
43 | );
44 | }
45 | });
46 | return matchedRoute && matchedRoute.component;
47 | };
48 |
49 | let routerPages = [];
50 | let oldRouterPages = [];
51 | let Pages = [];
52 | let oldLocation = null;
53 |
54 | class StackSwitch extends React.Component {
55 | static propsTypes = {
56 | routerConfig: PropsTypes.array.isRequired, //路由配置信息
57 | };
58 |
59 | constructor(props) {
60 | super(props);
61 |
62 | this.state = {
63 | hidePage: false,
64 | classNames: '',
65 | Pages: [],
66 | isClosePage: false,
67 | };
68 |
69 | this._isMounted = false;
70 |
71 | const { location, history } = this.props;
72 | let _this = this;
73 | _this._pendingLocation = location;
74 |
75 | history.listen(function (location) {
76 | if (_this._isMounted) {
77 | _this.refreshPage(location);
78 | } else {
79 | _this._pendingLocation = location;
80 | }
81 | });
82 | }
83 |
84 | componentDidMount() {
85 | let _this = this;
86 | _this._isMounted = true;
87 | if (_this._pendingLocation) {
88 | _this.refreshPage(_this._pendingLocation);
89 | this._pendingLocation = null;
90 | }
91 | }
92 |
93 | // 监听路由的history的变化,刷新页面
94 | refreshPage(location) {
95 | const { history, routerConfig } = this.props;
96 |
97 | let classNames = '';
98 | let isClosePage = false;
99 | let matchedComponent = getMatchedComponent(location, routerConfig);
100 | if (history.action === 'PUSH') {
101 | // 打开页面的动画,从配置项中获取,默认从右至左
102 | classNames = 'forward-' + getSceneConfig(location, routerConfig).enter;
103 | matchedComponent && routerPages.push(matchedComponent);
104 | Pages = routerPages;
105 | isClosePage = false;
106 | } else if (history.action === 'POP' && oldLocation) {
107 | // 关闭页面的动画,从配置项中获取,默认从左至右
108 | classNames = 'back-' + getSceneConfig(oldLocation, routerConfig).exit;
109 | routerPages.pop();
110 | Pages = oldRouterPages;
111 | isClosePage = true;
112 | } else {
113 | matchedComponent && routerPages.push(matchedComponent);
114 | Pages = routerPages;
115 | isClosePage = false;
116 | }
117 |
118 | // 更新旧location
119 | oldLocation = location;
120 | oldRouterPages = [].concat(routerPages);
121 |
122 | this.setState({
123 | classNames,
124 | Pages,
125 | routerPages,
126 | isClosePage,
127 | });
128 | }
129 |
130 | render() {
131 | const { classNames, Pages } = this.state;
132 | if (!Pages.length) return null;
133 | return (
134 |
135 | {Pages.map((Page, index) => {
136 | if (Page) {
137 | const className =
138 | index === Pages.length - 1
139 | ? classNames + '-active'
140 | : index === Pages.length - 2
141 | ? classNames
142 | : classNames + '-hide';
143 | return React.createElement(
144 | WrappedStackRoute(Page, className, index),
145 | Object.assign({}, { key: index }, { ...this.props }),
146 | null
147 | );
148 | }
149 | return null;
150 | })}
151 |
152 | );
153 | }
154 | }
155 |
156 | export default withRouter(StackSwitch);
157 |
--------------------------------------------------------------------------------
/src/router/WrapedComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './StackSwitch.css';
3 |
4 | function WrappedAnimatedRoute(WrappedComponent) {
5 | return class WrappedAnimatedRoute extends React.Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 | };
14 | }
15 |
16 | function WrappedStackRoute(WrappedComponent, className, index) {
17 | return class WrappedStackRoute extends React.Component {
18 | render() {
19 | return (
20 | {}}
26 | >
27 |
28 |
29 | );
30 | }
31 | };
32 | }
33 |
34 | export { WrappedAnimatedRoute, WrappedStackRoute };
35 |
36 | const styles = {
37 | container: {
38 | position: 'absolute',
39 | left: 0,
40 | top: 0,
41 | width: '100vw',
42 | height: '100vh',
43 | opacity: 1,
44 | },
45 | };
46 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------