h5 实现向左平滑,出现按钮操作,封装组件,模拟购物车左滑删除
需求背景
购物车用h5做页面?,那对手势的考验就很大了,毕竟h5操作手势不是那么的简单的,比如说点击事件和触摸事件的冲突、上下滑动触发左右的平滑事件等等,这些都是很难把握的冲突,但是我们今天就实现一个向左平滑的组件,操作手势,解决冲突。
其实不仅仅是购物车会用到向左平滑删除这个功能,其他的列表页也会有这种需求。下面我们就将其封装成组件,以供其他页面引用。
注:以下的代码使用vue
基础知识
1. 触摸事件
touchstart事件:事件对象event,包含手指触摸的位置
touchmove事件:事件对象event,包含手指滑动的位置
touchend事件:事件对象event,包含手指离开屏幕的位置
click事件:点击事件,和触摸事件冲突
2. 触摸事件和点击事件的,先后触发顺序
在屏幕上点击,先后触发事件
![](https://img.haomeiwen.com/i5393165/2b8153014d520f2a.png)
在屏幕上滑动,先后触发事件
![](https://img.haomeiwen.com/i5393165/016f744a667125f0.png)
注:从上图中我们可以看出,click时间是在touchstart之后触发的。滑动手势是不会触发click事件的
了解了以上的基础,现在我们就开始手写组件了
封装组件
1. 首先我们先考虑好html的结构
<div class="slide-operate">
<!-- 滑动块 -->
<div class="slide-operate-content" ref="slideBox">
<slot/> <!-- 滑动块部分,手势将都在滑动块上触发 -->
</div>
<!-- 滑动之后出现的按钮 -->
<div class="slide-btns" >
<!-- 按钮,可能有多个 -->
<div></div>
<div></div>
...
</div>
</div>
![](https://img.haomeiwen.com/i5393165/52eb0ca8b3d58ecd.png)
滑动滑动块,慢慢出现按钮
2. css样式部分
-
首先我们要注意的是,滑块部分我们不需要写样式,滑块部分是slot,所以这部分我们可以不考虑样式,主要就是按钮和整体的div。
-
其次初始样式,只是展示滑块,向左平滑慢慢出现按钮。
.slide-operate {
// 一开始按钮是隐藏的,所以使用overflow:hidden
position: relative;
overflow: hidden;
// 按钮部分,我们使用绝对定位
.slide-btns {
position: absolute;
top: 0;
// right: -70px;按钮一开始隐藏,right值应该是负的,值应该是按钮的宽度
color: #fff;
// width: 70px; 按钮的整体宽度,需要由传进来的值决定
height: 100%;
// 按钮可能有两个或者三个,所以使用flex布局
display: flex;
justify-content: space-between;
align-items: center;
> div {
// width: 100%; 因为按钮可能有多个,所以每个按钮的宽度应该是 (100/按钮个数)%
height: 100%;
background: #ff0024;
font-size: 0.15rem;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
}
}
3. 组件的属性设定(这些属性主要由父级传进)
-
首先,我们先设定向左滑动的距离(简单就是按钮的整体宽度),因为按钮可能有多个,按钮的宽度也需要响应调整,所以左滑出现的按钮宽度由父级传进
设定变量:distance -
其次,按钮个数和样式,以数组的形式传进
设定变量:btns -
第三,有些时候,我们并不希望滑块绑定滑动事件,比如说出现了弹窗,这个时候我们就不希望滑块出现滑动事件,所以我们由父级传进一个stop值,是否阻止滑动事件的触发
设定变量:stop
知道了父级传进的属性值,就可以将上面的css注释的部分补齐了
<div class="slide-operate">
<!-- 滑动块 -->
<div class="slide-operate-content" ref="slideBox">
<slot/>
</div>
<!-- 滑动之后出现的按钮 -->
<div class="slide-btns" :style="{width: `${distance}px`, right: `${-distance}px`" > <!-- 按钮的整体宽度,初始值right位置 -->
<!-- 每个按钮的宽度,背景色 -->
<div v-for="(item, i) in btns" :key="i" :style="{background: item.color, width: `${100 / btns.length}%` }">{{item.text}}</div>
</div>
</div>
4. 为滑块绑定触摸事件
// 在mounted钩子函数中,绑定滑动事件
mounted() {
// 获取滑块元素
let el = this.$refs.slideBox
// 绑定touchstart事件
el.addEventListener('touchstart', e => {
// stop阻止触摸事件
!this.stop && this.touchStart(e)
})
// 绑定touchmove事件
el.addEventListener('touchmove', e => {
!this.stop && this.touchMove(e)
})
// 绑定touchend事件
el.addEventListener('touchend', e => {
!this.stop && this.touchEnd(e)
})
},
methods: {
// 手指碰到屏幕
touchStart(e) {
},
// 手指移动
touchMove(e) {
},
// 手指离开屏幕
touchEnd(e) {
}
}
5. 理清楚滑动事件怎么进行
-
首先我们要先设定这个组件的全局变量
startX:手指触碰到屏幕时的x轴位置,为了在startmove事件中对比,获取在x轴移动的距离
move:手指移动的距离,主要是为了区分是点击事件还是触摸事件
moveDistance:手指移动距离加上滑动阻力后的移动距离
isShowBtn:右侧按钮是否已经出现 -
滑动函数处理
在touchStart函数中保存手指触碰屏幕时,获取的x轴的位置
touchStart(e) {
this.moveDistance = 0 // 阻力滑动距离归0
this.move = 0 // 移动距离归0
this.startX = e.targetTouches[0].clientX // x轴归0
}
在touchMove函数中,获取手指移动的距离,并且计算阻力距离
touchMove(e) {
// 获取左滑的距离
this.move = this.startX - e.targetTouches[0].clientX
// move大于0,说明手指移动了
if (this.move > 0) {
// 阻止默认事件,因为有些app会有默认事件
e.preventDefault()
// 增加滑动阻力,尤为重要,如果不要这一步,上下滑动就会不经意触发左右滑动事件
this.moveDistance = Math.pow(this.move, 0.8)
// 设置滑动的最大距离,如果阻力滑动的距离大于按钮的宽度,就赋值为按钮的宽度
if (this.moveDistance > this.distance) {
// 如果滑动距离大于15 出现隐藏按钮
this.moveDistance = this.distance
}
}
}
在touchEnd函数中,处理结束状态,如果阻力滑动距离大于按钮宽度的一半,则显示按钮,如果小于按钮宽度的一半,就归位。
touchEnd(e) {
// 如果滑动结束 滑动距离大于右侧按钮的一半 则出现按钮,否则隐藏按钮
if (this.moveDistance > this.distance / 2) {
this.moveDistance = this.distance
} else {
// 滑动距离并没有超过按钮的一半,给我归位
this.moveDistance = 0
}
}
}
到此,基本的滑动事件处理结束了,下面就是滑动的样式处理了
6. 滑动样式处理
我们选择在computed钩子函数中计算滑动的样式,这样就可以有按钮慢慢滑出的感觉了,样式这边我就不废话了,直接上代码
// 计算style样式
computed: {
style() {
return {
transition: `300ms`,
transform: `translate3d(${-this.moveDistance}px,0, 0)`
}
}
}
// html上加上style样式
<div class="slide-operate">
<!-- 滑动块 加上:style="style" -->
<div class="slide-operate-content" ref="slideBox" :style="style">
<slot/>
</div>
<!-- 滑动之后出现的按钮 加上style样式-->
<div class="slide-btns" :style="{width: `${distance}px`, right: `${-distance}px`,transition: `300ms`, transform: `translate3d(${-this.moveDistance}px,0, 0)`}" >
<div v-for="(item, i) in btns" :key="I" :style="{background: item.color, width: `${100 / btns.length}%` }" >{{item.text}}</div>
</div>
</div>
到此,基本的滑动事件已经结束了,可以正常使用了。
点击事件,触摸事件冲突处理
1. 为滑块增加点击事件
刚刚我们说到,点击事件和触摸事件的冲突,所以只能用触摸事件来代替点击事件了
我们默认,手指移动距离为0,就默认为点击事件
所以在touchEnd处理函数中根据move值判断是触摸事件还是点击事件
touchEnd(e) {
// ! this.isShowBtn,为了防止:当右侧按钮出现之后,点击滑动块,防止点击事件的触发
if (this.move == 0 && !this.isShowBtn) {
// 触发点击事件
this.$emit('handleclick')
} else {
// 滑动事件
// 如果滑动结束 滑动距离大于右侧按钮的一半 则出现按钮,否则隐藏按钮
if (this.moveDistance > this.distance / 2) {
this.moveDistance = this.distance
// 右侧按钮出现后 点击滑动块 防止出现触发点击事件
this.isShowBtn = true
} else {
this.moveDistance = 0
this.isShowBtn = false
}
}
}
到此,滑块的点击事件完美解决
2. 增加按钮的点击事件
<template>
<div class="slide-operate">
<!-- 滑动块 -->
<div class="slide-operate-content" ref="slideBox" :style="style">
<slot/>
</div>
<!-- 滑动之后出现的按钮 -->
<div class="slide-btns" :style="{width: `${distance}px`, right: `${-distance}px`,transition: `300ms`, transform: `translate3d(${-this.moveDistance}px,0, 0)`}" >
<div @click="handleBtn(i)" v-for="(item, i) in btns" :key="i" :style="{background: item.color, width: `${100 / btns.length}%` }">{{item.text}}</div>
</div>
</div>
</template>
// 点击按钮 index是指点击第几个按钮
handleBtn(index) {
this.$emit('handlebtn', index)
}
到此,完整的组件封装完毕,下面就是如何使用这个组件了
使用组件
- import 引入
import slideOperate from 'slide-operate'
- components注册
components: {
slideOperate
},
- 使用
<template>
<slide-operate :stop="stop" btns="[{text: '删除',color: '#E50012'}]" :distance="70" @handleclick="handelClick" @handlebtn="handleBtn">
// slot部分,就是商品块部分
</slide-operate>
</template>
methods: {
// 点击事件
handleClick() {
},
// 按钮点击事件
handleBtn(index) {
// index是点击的第几个按钮,从0开始
}
}