本次给你们分享的是常见的移动端单点触摸事件的设计思路及实践。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/…