本次給你們分享的是常見的移動端單點觸摸事件的設計思路及實踐。node
主要就是利用移動端的如下3個觸摸事件,來模擬和實現自定義的手勢操做git
touch事件觸發時,有3組數據能夠得到觸摸信息,可能你們會對這幾組數據有些混淆,我根據本身的理解來儘可能用通俗的語言給你們解釋清楚github
咱們先來看一張圖數組
如圖所示,咱們在節點B上綁定touch事件,圓圈表明觸摸點。瀏覽器
此時節點B有3個觸摸點,即targetTouches
數組有3項,分別儲存着觸摸點的信息,此時touches
和targetTouches
是相同的。動畫
當咱們將手指3移出節點B(始終保持3個手指觸摸在屏幕上),那麼touchmove事件觸發,targetTouches
只剩2項,而touches
依然有3項,此時changedTouches
只有一項(由於只有手指3改變了)。ui
而後咱們讓全部手指離開屏幕,那麼此時touchend事件觸發,touches
只剩0項,targetTouches
剩0項,changedTouches
有3項(由於3個手指發生了變化)。spa
好了,理解這些概念,有助於咱們理解代碼中什麼時候改去哪一個touch數組裏面的值。設計
tap能夠理解爲點擊事件,和click不一樣的是,移動端的click事件有大約300ms的延遲,這是由於瀏覽器要判斷是否爲雙擊事件。code
思路
說明:時間差用來判斷用戶觸摸的時長,超過規定時間則tap事件無效;偏移量用來判斷用戶的觸摸事件內是否有過移動的痕跡,這裏咱們容許少許的偏移,由於手指可能出現抖動的狀況
實現
const tapDefaults = {
time: 250,
offset: 10
}
export default function tap (node, a, b) {
let st, sx, sy
let opts, callback
if (typeof a === 'function') {
callback = a
opts = Object.assign({}, tapDefaults, b)
} else {
callback = b
opts = Object.assign({}, tapDefaults, a)
}
node.addEventListener('touchstart', (e) => {
e.preventDefault() // 組織瀏覽器默認行爲,防止觸摸過程頁面滾動
const touch = e.targetTouches[0]
st = e.timeStamp
sx = touch.pageX
sy = touch.pageY
}, false)
node.addEventListener('touchend', (e) => {
const touch = e.changedTouches[0]
if (
// 若爲長按,則將時間斷定條件更改
e.timeStamp - st <= opts.time &&
Math.abs(touch.pageX - sx) <= opts.offset &&
Math.abs(touch.pageY - sy) <= opts.offset
) {
callback && callback()
}
}, false)
}
複製代碼
即雙擊事件,兩次點擊時間間隔不超過規定時間則視爲有效。
思路
實現
const tapDefaults = {
time: 250,
offset: 10
}
function handler (node, inject) {
let st, sx, sy
node.addEventListener('touchstart', (e) => {
e.preventDefault()
const touch = e.targetTouches[0]
st = e.timeStamp
sx = touch.pageX
sy = touch.pageY
}, false)
node.addEventListener('touchend', (e) => {
const touch = e.changedTouches[0]
inject({
time: e.timeStamp - st,
offsetX: Math.abs(touch.pageX - sx),
offsetY: Math.abs(touch.pageY - sy)
})
}, false)
}
export function doubletap (node, a, b) {
let opts, callback
let status = 0
if (typeof a === 'function') {
callback = a
opts = Object.assign({}, tapDefaults, b)
} else {
callback = b
opts = Object.assign({}, tapDefaults, a)
}
handler(node, (info) => {
if (
info.time <= opts.time &&
info.offsetX <= opts.offset &&
info.offsetY <= opts.offset
) {
if (status === 0) {
status = 1
// 時間間隔太長則重置狀態
setTimeout(() => {
status = 0
}, opts.time)
} else if (status === 1) {
callback && callback()
status = 0
}
} else {
status = 0
}
})
}
複製代碼
即長按,手指按住超過規定時間視爲有效,在手指離開時觸發。
思路
實現
const longtapDefaults = {
time: 350,
offset: 10
}
// 這裏代碼邏輯和tap事件同樣
// 更改時間斷定爲:
// e.timeStamp - st > opts.time
複製代碼
即按壓事件,按住超過規定時間自動觸發,注意和longtap
不一樣的是,longtap須要等到手指離開時觸發,而press
在按壓時間達到規定值,自動觸發,此時手指還在屏幕上。
思路
分析:根據以上思路,若按壓時間短,則手指離開時定時器已取消,回調不會觸發。
實現
const pressDefaults = {
time: 350,
offset: 10
}
export default function press (node, a, b) {
let opts, callback, sx, sy
let timer = null
if (typeof a === 'function') {
callback = a
opts = Object.assign({}, pressDefaults, b)
} else {
callback = b
opts = Object.assign({}, pressDefaults, a)
}
node.addEventListener('touchstart', (e) => {
e.preventDefault()
const touch = e.targetTouches[0]
sx = touch.pageX
sy = touch.pageY
timer = setTimeout(() => {
callback && callback()
}, opts.time)
}, false)
node.addEventListener('touchmove', (e) => {
const touch = e.targetTouches[0]
if (
Math.abs(touch.pageX - sx) > opts.offset ||
Math.abs(touch.pageY - sy) > opts.offset
) {
clearTimeout(timer)
}
}, false)
node.addEventListener('touchend', () => {
clearTimeout(timer)
}, false)
}
複製代碼
即手指滑動事件,應用場景如:輪播圖左右滑動切換,整屏頁面滑動翻頁等,算是移動端最多見的手勢之一了。
思路
分析:考慮到須要在滑動過程作一些動畫特效等操做,所以咱們將滑動中的事件暴露給用戶自定義,值得注意的是,如果要實時改變滑塊位置的話,最好不要截流或防抖,截流會形成滑動卡頓的現象,而防抖會出現延遲同步滑動操做的狀況;另外對滑動速度也進行了處理,原則上用戶滑動距離超過規定後即視爲有效,然而爲了更好的用戶體驗,咱們斷定,若是用戶在短期內滑動速度很是快的話,也視爲一次有效的操做,不必定非要滑動很長的距離
實現
const swipeDefaults = {
direction: 'horizontal', // vertical
speed: 200,
offset: 100,
prevent: true,
// touchmove: (offset) => {}
}
export default function swipe (node, a, b) {
let opts, callback, sTime, sTouch, eTouch
if (typeof a === 'function') {
callback = a
opts = Object.assign({}, swipeDefaults, b)
} else {
callback = b
opts = Object.assign({}, swipeDefaults, a)
}
node.addEventListener('touchstart', (e) => {
if (opts.prevent) {
e.preventDefault()
}
sTime = e.timeStamp
sTouch = eTouch = e.targetTouches[0]
}, false)
if (typeof opts.touchmove === 'function') {
node.addEventListener('touchmove', (e) => {
eTouch = e.targetTouches[0]
if (opts.direction === 'horizontal') {
opts.touchmove(eTouch.pageX - sTouch.pageX)
} else {
opts.touchmove(eTouch.pageY - sTouch.pageY)
}
}, false)
}
node.addEventListener('touchend', (e) => {
eTouch = e.changedTouches[0]
let time = e.timeStamp - sTime
let offset, direction
if (opts.direction === 'horizontal') {
offset = eTouch.pageX - sTouch.pageX
direction = offset > 0 ? 'right' : 'left'
} else {
offset = eTouch.pageY - sTouch.pageY
direction = offset > 0 ? 'down' : 'up'
}
if (
Math.abs(offset) >= opts.offset ||
Math.abs(offset) / time * 1000 >= opts.speed
) {
callback && callback(direction)
}
}, false)
}
複製代碼
經過以上的思路講解和代碼實現,咱們完成了一個單點觸控的移動端手勢庫,是否是火燒眉毛的想要一睹爲快、體驗一番。
最後附上本次分享的源碼和文檔:github.com/ansenhuang/…