项目中需要实现的一个需求是顶部有一个tab选择框,点选某一个tab的时候切换页面,并且支持手势滑动,左滑右滑可以同点选tab一样切换页面。
根据项目中选用的ui组件cube-ui为基准查看了一下可实现的方案,比如可以直接用swipe或者是slide实现,但根据之前的实现方案来看,多少都会有些问题,尤其是在页面嵌套了很多层的垂直和水平滚动的情况下,会让滚动很不流畅。
于是选用了移动端的touch事件,整个功能就是监听页面touchstart,touchmove,touchend这三个事件来做的。
首先页面结构如下:
<div class="home-main flex-1" @='touchStart' @="touchMove" @='touchEnd'>
<transition :name="transitionName" mode="out-in">
<keep-alive>
<router-view/>
</keep-alive>
</transition>
</div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在router-view里面就是需要进行切换的页面,页面切换不能太生硬需要加入动画,并且左滑右滑的动画效果是不一样的。
那么接下来就是滑动样式:
.slide-right-enter-active {
transition: all .8s ease;
}
.slide-right-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-right-enter {
opacity: 0;
transform: translateX(-100%);
}
.slide-right-leave-to {
opacity: 0;
transform: translateX(100%);
}
.slide-left-enter-active {
transition: all .8s ease;
}
.slide-left-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-left-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-left-leave-to {
opacity: 0;
transform: translateX(-100%);
}
- 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
然后就是主要实现方法了,首先data里面声明touch事件需要用到的数据:
data () {
transitionName: '', // 过度动画名称 滑动时才有过度动画
touch: {}, // 保存着起始位置x1和变化的位置x2
currentDistance: 0, // 上一个touch事件完成后,已滑动距离。实际在这个设计里,因为我们手指离开后, 页面不会停留在中间,不是滑过去切换路由,就是滑回去恢复原样。所以这个变量并没有什么卵用,但是如果要*即停即走*,这个变量不可少。
totalDiff: 0 // 总滑动距离
}
- 1
- 2
- 3
- 4
- 5
- 6
然后touchStart方法很简单,只需要记录一下手指初始接触页面的位置即可:
touchStart (ev) {
let touch = [0]
.x1 =
.y1 =
}
- 1
- 2
- 3
- 4
- 5
左滑右滑肯定是手指在页面滑动了一定的距离才触发,所以滑动过程中也需要记录下位置,touchMove方法:
touchMove (ev) {
let touch = [0]
.x2 =
.y2 =
let w = (.x2 - .x1)
let h = (.y2 - .y1)
// 部分安卓手机可能会有兼容问题
if (w > h) {
()
// ()
}
= .x2 - .x1 // 差值,表示滑动过程中手指移动的距离
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
因为vue中的click300延迟以及touchstart可能和click的各种兼容问题,这里需要判断处理下是否左滑,并且将页面的默认滑动先禁止。
然后在touchend中将手指离开的坐标记录下,同时将路由切换到滑动页面:
// TODO:此方法复用性差 需要再次封装
touchEnd (ev) {
let touch = [0]
.x2 =
.y2 =
let diff = .x2 - .x1 // 手指触摸结束位置的水平移动差值
let diffY = .y2 - .y1 // 竖直方向的差值
= diff +
// 正切得到弧度转换为角度
let angel = Math.atan2((diffY), ()) * 180 / () // 90为垂直滑动 需要一定差值
if () { // 这个参数是为了处理页面banner的滑动,水平滑动和手势的冲突
if (angel < 45 && < -20 && this.$ !== 'homeVideo') { // 小于为左滑 左滑到右边(视频)
= 'slide-left'
this.$('/home/video')
= '视频'
} else if (angel < 30 && > 20 && this.$ !== 'homeFound') { // 大于为右滑 右滑到左边(发现)
= 'slide-right'
this.$('/home/Found')
= '发现'
}
}
this.$('setTouchStatus', true)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
在结束滑动的时候判断下是左滑还是右滑,并且允许一定的角度差,因为人的手指是比较粗的,而且滑动的时候并不是水平或者竖直的,存在一定的偏移,所以要控制一个偏移距离和角度,让滑动顺畅一些,而不是滑动着就切换页面了,以上偏移角度和长度只是项目测试的可接受数据,并不是标准的值。
至此,页面的左滑右滑切换就完成了,但是随之问题也出现了,比如页面头部有个banner,中间内容部分还有可以水平滚动的区块,我在滑动banner,或者滑动水平区块的时候,页面直接切换了,这并不是我想要的结果。
虽然touch事件是添加到外层容器元素上面的,但touch事件是可以冒泡的,所以现在的思路就是阻止右水平滑动的几个元素冒泡不就完事了,然后我就一顿添加vue的修饰符,@简直不要太轻松,事实证明我还是太年轻,添加之后并没有什么用,反而让页面的滚动行为异常,再接再厉,@让事件捕获,然后以防万一我再阻止冒泡stopPropagation,果然也是没什么用,页面的各种滚动变得奇奇怪怪,要么滚不动要么一起滚,总之就是不能正常 。
然后换个思路,我可以在水平滚动那几个元素的时候让绑定的touch事件不触发,从而让它自己滚动,页面也不会切换,nice。于是有了以下方法:
preventTouch (theNode) {
let that = this
let flag = false // 是否滚动
const scrollNode = (theNode) // 有滚动的面板
let dur = 0 // 触摸时间,太短不触发
let dateStart, dateEnd
('touchstart', handler, { passive: false })
('touchmove', handler, { passive: false })
('touchend', handler, { passive: false })
('scroll', handler, { passive: false })
function handler (e) {
switch () {
case 'touchstart':
flag = false
dateStart = new Date()
that.$('setTouchStatus', false)
break
case 'touchmove':
dateEnd = new Date()
dur = dateEnd - dateStart
if (flag || dur < 300) { // 表示是滚动完成后的那个touch不触发/间隔太短
// () // 阻止冒泡 可能会导致滑动不流畅
that.$('setTouchStatus', false)
}
break
case 'touchend':
dateEnd = new Date()
dur = dateEnd - dateStart
if (flag || dur < 300) { // 表示是滚动完成后的那个touch不触发/间隔太短
// () // 阻止冒泡
that.$('setTouchStatus', true) // 区块横向滑动完毕将状态置为可滑动
}
break
case 'scroll':
if (!flag) {
flag = true
}
break
}
}
}
- 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
添加一个全局状态canTouch,如果是在页面可以水平滚动的区块,那么touchEnd事件就不执行页面的切换和动画,也就是说,并不去干涉页面的滚动行为,该滚还是让他滚,也不禁止冒泡和默认行为,这样也免去了滚动异常,然后在各区块独立水平滚动的同时,在这些元素范围外可以左右滑动切换。
后续还需要重新封装一下这些方法,看看有没有更优雅的解决方案,同时touch也可以封装成指令,方便使用。