vue-router的路由变换只存在“变换前”和“变换后”,不存在“切换中”的状态,所以做不到大多数app(微信那样的)在滑动过程中让界面跟随手指移动。但滑动事件还是可以监听的,我们可以在滑动之后再触发路由回退事件。
微博的滑动返回基本上就是这样的原理:先滑动、再触发返回事件,但用起来很是怪异,有严重的滞后感。夸克浏览器做的就比较好:一是滑动时界面虽然不动,但是界面上有小图标提示,能让用户接受到反馈;二是返回过程很快,没有多余的过渡动画。
app.vue文件如下:
1 <template>
2 <div id="app" v-on:touchstart="bodyTouchStart" v-on:touchmove="bodyTouchMove" v-on:touchend="bodyTouchEnd">
3 <transition :name="direction">
4 <keep-alive include="home">
5 <router-view class="appView"></router-view>
6 </keep-alive>
7 </transition>
8 </div>
9 </template>
10
11 <script>
12 var swidth = document.documentElement.clientWidth;
13
14 export default {
15 name: 'app',
16 data: () => ({
17 // direction 页面切换的过渡动画,配合transition组件使用
18 direction: "slide-left",
19 // touchLeft 划动起点界限,起点在靠近屏幕左侧时才有效
20 touchLeft: swidth*2/5,
21 // touchStartPoint 记录起始点X坐标
22 touchStartPoint: 0,
23 // distance 记录划动的距离
24 distance: 0,
25 // 回退按钮的dom,根据页面上是否存在此dom来判断该路由是否可回退
26 backBtn: null
27 }),
28
29 watch: {
30 // 监听路有变化,决定页面过渡动画
31 $route(to, from) {
32 if (from.name == "login" || from.path.indexOf("home") > -1) {
33 this.direction = "slide-left";
34 } else if (to.path.indexOf("home") > -1) {
35 this.direction = "slide-right";
36 } else {
37 const toDepth = to.path.split("/").length;
38 const fromDepth = from.path.split("/").length;
39 this.direction = toDepth < fromDepth ? "slide-right" : "slide-left";
40 }
41 }
42 },
43
44 methods: {
45 bodyTouchStart: function(event) {
46 this.backBtn = document.getElementById("navback");
47 if (this.backBtn) {
48 // 获得起点X坐标,初始化distance为0
49 this.touchStartPoint = event.targetTouches[0].pageX;
50 this.distance = 0;
51 }
52 },
53 bodyTouchMove: function(event) {
54 if (this.backBtn && this.touchStartPoint < this.touchLeft) {
55 // 只监听单指划动,多指划动不作响应
56 if (event.targetTouches.length > 1) {
57 return;
58 }
59 // 实时计算distance
60 this.distance = event.targetTouches[0].pageX - this.touchStartPoint;
61 // 根据distance在页面上做出反馈。这里演示通过返回按钮的背景变化作出反馈
62 if (this.distance > 0 && this.distance < 100) {
63 this.backBtn.style.backgroundPosition = ((this.distance - 100) / 100) * 50 "px 0";
64 } else if (this.distance >= 100) {
65 this.backBtn.style.backgroundPosition = "0 0";
66 } else {
67 this.backBtn.style.backgroundPosition = "-50px 0";
68 }
69 }
70 },
71 bodyTouchEnd: function(event) {
72 if (this.backBtn && this.touchStartPoint < this.touchLeft) {
73 // 划动结束,重置数据
74 this.touchStartPoint = 0;
75 this.backBtn.style.backgroundPosition = "-50px 0";
76 // 当划动距离超过100px时,触发返回事件
77 if (this.distance > 100) {
78 // 返回前修改样式,让过渡动画看起来更快
79 document.getElementById("app").classList.add("quickback");
80 this.$router.back();
81 setTimeout(function(){
82 document.getElementById("app").classList.remove("quickback");
83 },250)
84 }
85 }
86 }
87 }
88 }
89 </script>
90
91 <style>
92 #app {
93 -webkit-font-smoothing: antialiased;
94 -moz-osx-font-smoothing: grayscale;
95 width: 100%;
96 overflow-x: hidden;
97 }
98 .appView {
99 position: absolute;
100 width: 100%;
101 background: #fff;
102 min-height: 100vh;
103 transition: transform 0.24s ease-out;
104 }
105 #app.quickback .appView{
106 transition-duration: 0.1s;
107 }
108 .slide-left-enter {
109 transform: translate(100%, 0);
110 }
111 .slide-left-leave-active {
112 transform: translate(-50%, 0);
113 }
114 .slide-right-enter {
115 transform: translate(-50%, 0);
116 }
117 .slide-right-leave-active {
118 transform: translate(100%, 0);
119 }
120 </style>
代码案例见 https://github.com/yource/VueSPA