需求背景
購物車用h5做頁面锄开?素标,那對(duì)手勢的考驗(yàn)就很大了,畢竟h5操作手勢不是那么的簡單的萍悴,比如說點(diǎn)擊事件和觸摸事件的沖突头遭、上下滑動(dòng)觸發(fā)左右的平滑事件等等,這些都是很難把握的沖突退腥,但是我們今天就實(shí)現(xiàn)一個(gè)向左平滑的組件任岸,操作手勢再榄,解決沖突狡刘。
其實(shí)不僅僅是購物車會(huì)用到向左平滑刪除這個(gè)功能,其他的列表頁也會(huì)有這種需求困鸥。下面我們就將其封裝成組件嗅蔬,以供其他頁面引用。
注:以下的代碼使用vue
基礎(chǔ)知識(shí)
1. 觸摸事件
touchstart事件:事件對(duì)象event疾就,包含手指觸摸的位置
touchmove事件:事件對(duì)象event澜术,包含手指滑動(dòng)的位置
touchend事件:事件對(duì)象event,包含手指離開屏幕的位置
click事件:點(diǎn)擊事件猬腰,和觸摸事件沖突
2. 觸摸事件和點(diǎn)擊事件的鸟废,先后觸發(fā)順序
在屏幕上點(diǎn)擊,先后觸發(fā)事件
在屏幕上滑動(dòng)盒延,先后觸發(fā)事件
注:從上圖中我們可以看出,click時(shí)間是在touchstart之后觸發(fā)的添寺】瓒ⅲ滑動(dòng)手勢是不會(huì)觸發(fā)click事件的
了解了以上的基礎(chǔ),現(xiàn)在我們就開始手寫組件了
封裝組件
1. 首先我們先考慮好html的結(jié)構(gòu)
<div class="slide-operate">
<!-- 滑動(dòng)塊 -->
<div class="slide-operate-content" ref="slideBox">
<slot/> <!-- 滑動(dòng)塊部分计露,手勢將都在滑動(dòng)塊上觸發(fā) -->
</div>
<!-- 滑動(dòng)之后出現(xiàn)的按鈕 -->
<div class="slide-btns" >
<!-- 按鈕博脑,可能有多個(gè) -->
<div></div>
<div></div>
...
</div>
</div>
滑動(dòng)滑動(dòng)塊,慢慢出現(xiàn)按鈕
2. css樣式部分
首先我們要注意的是票罐,滑塊部分我們不需要寫樣式叉趣,滑塊部分是slot,所以這部分我們可以不考慮樣式该押,主要就是按鈕和整體的div君账。
其次初始樣式,只是展示滑塊沈善,向左平滑慢慢出現(xiàn)按鈕乡数。
.slide-operate {
// 一開始按鈕是隱藏的,所以使用overflow:hidden
position: relative;
overflow: hidden;
// 按鈕部分闻牡,我們使用絕對(duì)定位
.slide-btns {
position: absolute;
top: 0;
// right: -70px净赴;按鈕一開始隱藏,right值應(yīng)該是負(fù)的罩润,值應(yīng)該是按鈕的寬度
color: #fff;
// width: 70px; 按鈕的整體寬度玖翅,需要由傳進(jìn)來的值決定
height: 100%;
// 按鈕可能有兩個(gè)或者三個(gè),所以使用flex布局
display: flex;
justify-content: space-between;
align-items: center;
> div {
// width: 100%; 因?yàn)榘粹o可能有多個(gè)金度,所以每個(gè)按鈕的寬度應(yīng)該是 (100/按鈕個(gè)數(shù))%
height: 100%;
background: #ff0024;
font-size: 0.15rem;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
}
}
3. 組件的屬性設(shè)定(這些屬性主要由父級(jí)傳進(jìn))
首先消玄,我們先設(shè)定向左滑動(dòng)的距離(簡單就是按鈕的整體寬度)受扳,因?yàn)榘粹o可能有多個(gè),按鈕的寬度也需要響應(yīng)調(diào)整层亿,所以左滑出現(xiàn)的按鈕寬度由父級(jí)傳進(jìn)
設(shè)定變量:distance其次匿又,按鈕個(gè)數(shù)和樣式洞慎,以數(shù)組的形式傳進(jìn)
設(shè)定變量:btns第三旭绒,有些時(shí)候,我們并不希望滑塊綁定滑動(dòng)事件,比如說出現(xiàn)了彈窗,這個(gè)時(shí)候我們就不希望滑塊出現(xiàn)滑動(dòng)事件,所以我們由父級(jí)傳進(jìn)一個(gè)stop值,是否阻止滑動(dòng)事件的觸發(fā)
設(shè)定變量:stop
知道了父級(jí)傳進(jìn)的屬性值,就可以將上面的css注釋的部分補(bǔ)齊了
<div class="slide-operate">
<!-- 滑動(dòng)塊 -->
<div class="slide-operate-content" ref="slideBox">
<slot/>
</div>
<!-- 滑動(dòng)之后出現(xiàn)的按鈕 -->
<div class="slide-btns" :style="{width: `${distance}px`, right: `${-distance}px`" > <!-- 按鈕的整體寬度该抒,初始值right位置 -->
<!-- 每個(gè)按鈕的寬度冈爹,背景色 -->
<div v-for="(item, i) in btns" :key="i" :style="{background: item.color, width: `${100 / btns.length}%` }">{{item.text}}</div>
</div>
</div>
4. 為滑塊綁定觸摸事件
// 在mounted鉤子函數(shù)中芝此,綁定滑動(dòng)事件
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) {
},
// 手指移動(dòng)
touchMove(e) {
},
// 手指離開屏幕
touchEnd(e) {
}
}
5. 理清楚滑動(dòng)事件怎么進(jìn)行
首先我們要先設(shè)定這個(gè)組件的全局變量
startX:手指觸碰到屏幕時(shí)的x軸位置怎炊,為了在startmove事件中對(duì)比糟港,獲取在x軸移動(dòng)的距離
move:手指移動(dòng)的距離秸抚,主要是為了區(qū)分是點(diǎn)擊事件還是觸摸事件
moveDistance:手指移動(dòng)距離加上滑動(dòng)阻力后的移動(dòng)距離
isShowBtn:右側(cè)按鈕是否已經(jīng)出現(xiàn)滑動(dòng)函數(shù)處理
在touchStart函數(shù)中保存手指觸碰屏幕時(shí),獲取的x軸的位置
touchStart(e) {
this.moveDistance = 0 // 阻力滑動(dòng)距離歸0
this.move = 0 // 移動(dòng)距離歸0
this.startX = e.targetTouches[0].clientX // x軸歸0
}
在touchMove函數(shù)中歹垫,獲取手指移動(dòng)的距離剥汤,并且計(jì)算阻力距離
touchMove(e) {
// 獲取左滑的距離
this.move = this.startX - e.targetTouches[0].clientX
// move大于0,說明手指移動(dòng)了
if (this.move > 0) {
// 阻止默認(rèn)事件排惨,因?yàn)橛行゛pp會(huì)有默認(rèn)事件
e.preventDefault()
// 增加滑動(dòng)阻力吭敢,尤為重要,如果不要這一步暮芭,上下滑動(dòng)就會(huì)不經(jīng)意觸發(fā)左右滑動(dòng)事件
this.moveDistance = Math.pow(this.move, 0.8)
// 設(shè)置滑動(dòng)的最大距離鹿驼,如果阻力滑動(dòng)的距離大于按鈕的寬度,就賦值為按鈕的寬度
if (this.moveDistance > this.distance) {
// 如果滑動(dòng)距離大于15 出現(xiàn)隱藏按鈕
this.moveDistance = this.distance
}
}
}
在touchEnd函數(shù)中辕宏,處理結(jié)束狀態(tài)畜晰,如果阻力滑動(dòng)距離大于按鈕寬度的一半,則顯示按鈕瑞筐,如果小于按鈕寬度的一半凄鼻,就歸位。
touchEnd(e) {
// 如果滑動(dòng)結(jié)束 滑動(dòng)距離大于右側(cè)按鈕的一半 則出現(xiàn)按鈕,否則隱藏按鈕
if (this.moveDistance > this.distance / 2) {
this.moveDistance = this.distance
} else {
// 滑動(dòng)距離并沒有超過按鈕的一半块蚌,給我歸位
this.moveDistance = 0
}
}
}
到此闰非,基本的滑動(dòng)事件處理結(jié)束了,下面就是滑動(dòng)的樣式處理了
6. 滑動(dòng)樣式處理
我們選擇在computed鉤子函數(shù)中計(jì)算滑動(dòng)的樣式峭范,這樣就可以有按鈕慢慢滑出的感覺了财松,樣式這邊我就不廢話了,直接上代碼
// 計(jì)算style樣式
computed: {
style() {
return {
transition: `300ms`,
transform: `translate3d(${-this.moveDistance}px,0, 0)`
}
}
}
// html上加上style樣式
<div class="slide-operate">
<!-- 滑動(dòng)塊 加上:style="style" -->
<div class="slide-operate-content" ref="slideBox" :style="style">
<slot/>
</div>
<!-- 滑動(dòng)之后出現(xiàn)的按鈕 加上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>
到此纱控,基本的滑動(dòng)事件已經(jīng)結(jié)束了辆毡,可以正常使用了。
點(diǎn)擊事件其徙,觸摸事件沖突處理
1. 為滑塊增加點(diǎn)擊事件
剛剛我們說到胚迫,點(diǎn)擊事件和觸摸事件的沖突,所以只能用觸摸事件來代替點(diǎn)擊事件了
我們默認(rèn)唾那,手指移動(dòng)距離為0访锻,就默認(rèn)為點(diǎn)擊事件
所以在touchEnd處理函數(shù)中根據(jù)move值判斷是觸摸事件還是點(diǎn)擊事件
touchEnd(e) {
// ! this.isShowBtn,為了防止:當(dāng)右側(cè)按鈕出現(xiàn)之后闹获,點(diǎn)擊滑動(dòng)塊期犬,防止點(diǎn)擊事件的觸發(fā)
if (this.move == 0 && !this.isShowBtn) {
// 觸發(fā)點(diǎn)擊事件
this.$emit('handleclick')
} else {
// 滑動(dòng)事件
// 如果滑動(dòng)結(jié)束 滑動(dòng)距離大于右側(cè)按鈕的一半 則出現(xiàn)按鈕,否則隱藏按鈕
if (this.moveDistance > this.distance / 2) {
this.moveDistance = this.distance
// 右側(cè)按鈕出現(xiàn)后 點(diǎn)擊滑動(dòng)塊 防止出現(xiàn)觸發(fā)點(diǎn)擊事件
this.isShowBtn = true
} else {
this.moveDistance = 0
this.isShowBtn = false
}
}
}
到此避诽,滑塊的點(diǎn)擊事件完美解決
2. 增加按鈕的點(diǎn)擊事件
<template>
<div class="slide-operate">
<!-- 滑動(dòng)塊 -->
<div class="slide-operate-content" ref="slideBox" :style="style">
<slot/>
</div>
<!-- 滑動(dòng)之后出現(xiàn)的按鈕 -->
<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>
// 點(diǎn)擊按鈕 index是指點(diǎn)擊第幾個(gè)按鈕
handleBtn(index) {
this.$emit('handlebtn', index)
}
到此龟虎,完整的組件封裝完畢,下面就是如何使用這個(gè)組件了
使用組件
- 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: {
// 點(diǎn)擊事件
handleClick() {
},
// 按鈕點(diǎn)擊事件
handleBtn(index) {
// index是點(diǎn)擊的第幾個(gè)按鈕鲤妥,從0開始
}
}