页面切换动画

不管是在过去的项目还是未来的项目(SPA 项目)中我们都会遇到大量的页面切换,往往伴随页面切换的时候会要求一些切换效果,如:

/right.gif

原理

在路由组件进入/创建时添加一个出场动画,在路由组件离开/销毁前添加一个出场动画。这样就实现了路由切换的动画效果,是不是很简单 😄

实现

光说原理,不谈实现就是耍流氓,下面使用三大框架分别实现路由切换的效果

vue

由于 vue 有transition组件,所以 vue 中直接使用路由和transition实现

vue 2.x

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<template>
<div id="app">
<div id="nav" :data="transition">
<router-link to="/a">Page A</router-link> |
<router-link to="/b">Page B</router-link> |
<router-link to="/c">Page C</router-link>
</div>
<transition :name="transition">
<router-view class="child-view"></router-view>
</transition>
</div>
</template>

<script>
export default {
name: "App",
data() {
return {
transition: "",
lastRouteIndex: 0,
};
},
watch: {
$route(val) {
if (val.meta.index >= this.lastRouteIndex) {
this.transition = "right-slide-in";
} else {
this.transition = "left-slide-in";
}
this.lastRouteIndex = val.meta.index;
},
},
};
</script>

<style>
/* ... */

.child-view {
width: 100%;
position: absolute;
transition: all 0.8s cubic-bezier(0.55, 0, 0.1, 1);
}

.right-slide-in-enter {
opacity: 0;
transform: translate(20%, 0);
}
.right-slide-in-leave-active {
opacity: 0;
}

.left-slide-in-enter {
opacity: 0;
transform: translate(-20%, 0);
}
.left-slide-in-leave-active {
opacity: 0;
}
</style>

vue3.0

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
<div id="nav" :data="transition">
<router-link to="/a">Page A</router-link> |
<router-link to="/b">Page B</router-link> |
<router-link to="/c">Page C</router-link>
</div>
<router-view v-slot="{ Component }">
<transition :name="transition">
<component class="child-view" :is="Component" />
</transition>
</router-view>
</template>

<script lang="ts">
import { watch, defineComponent, ref } from "vue";
import { useRoute, RouteLocationNormalizedLoaded } from "vue-router";

export default defineComponent({
setup() {
let lastRouteIndex = 0;
const route = useRoute();
const transition = ref("right-slide-in");

watch(
() => route.meta.index,
(index: number) => {
if (index >= lastRouteIndex) {
transition.value = "right-slide-in";
} else {
transition.value = "left-slide-in";
}
lastRouteIndex = index;
},
{ flush: "pre" }
);

return {
transition: transition,
};
},
});
</script>

<style>
/* ... */

.child-view {
width: 100%;
position: absolute;
transition: all 0.8s cubic-bezier(0.55, 0, 0.1, 1);
}

.right-slide-in-enter-from {
opacity: 0;
transform: translate(20%, 0);
}
.right-slide-in-leave-active {
opacity: 0;
}

.left-slide-in-enter-from {
opacity: 0;
transform: translate(-20%, 0);
}
.left-slide-in-leave-active {
opacity: 0;
}
</style>

react

react 需要使用react-transition-group这个库。

需要注意的是react-transition-group与 Vue 的 transitin 在动画上有一些区别,具体来讲他们的动画名不一样。另外 react 路由在切换的时候就已经更新了。

另外 react-router 不能使用 vue-router 的自定义路由元参数,确实不好处理,我这里是简单的写到 app 中,并不算是完美的解决办法。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
const indexs: { [key: string]: number } = {
"/a": 0,
"/b": 1,
"/c": 2,
};

class App extends Component<RouteComponentProps> {
currentIndex = 0;

render() {
const { location } = this.props;
const index = indexs[location.pathname] || 0;
const rightIn = index >= this.currentIndex;
const className = rightIn ? "right-slide-in" : "left-slide-in";
this.currentIndex = index;
return (
<div id="app">
<div id="nav">
<NavLink to="/a" activeClassName="router-link-exact-active">
Page A
</NavLink>
|<NavLink
to="/b"
activeClassName="router-link-exact-active"
>
Page B
</NavLink>|<NavLink
to="/c"
activeClassName="router-link-exact-active"
>
Page C
</NavLink>
</div>
<TransitionGroup>
<CSSTransition
timeout={800}
key={location.key}
classNames={"child-view " + className}
>
<Switch>
<Route exact path="/">
<Redirect to={{ pathname: "/a" }} />
</Route>
<Route exact path="/a">
<A />
</Route>
<Route exact path="/b">
<B />
</Route>
<Route exact path="/c">
<C />
</Route>
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
);
}
}
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
.child-view {
width: 100%;
position: absolute;
transition: all 0.8s cubic-bezier(0.55, 0, 0.1, 1);
}

.right-slide-in-enter {
opacity: 0;
transform: translate(20%, 0);
}
.right-slide-in-enter-active {
opacity: 1;
transform: translate(0, 0);
}
.right-slide-in-exit {
opacity: 1;
}
.right-slide-in-exit-active {
opacity: 0;
}

.left-slide-in-enter {
opacity: 0;
transform: translate(-20%, 0);
}
.left-slide-in-enter-active {
opacity: 1;
transform: translate(0, 0);
}
.left-slide-in-exit {
opacity: 1;
}
.left-slide-in-exit-active {
opacity: 0;
}

angular

有快 2 年的时间没有写 angular 了,都要忘记 angular 了。但不的不说 angular 还是一如既往的强大,中文文档也做的很不错。参考官方文档很容易就写出来了,这里就不贴代码了,移步https://angular.cn/guide/route-animations

demo 源码

https://github.com/qietuzi/blog-demo/tree/master/SPA 页面切换动画

[越努力,越幸运!]