本次给你们分享的是常见的移动端单点触摸事件的设计思路及实践。node
主要就是利用移动端的如下3个触摸事件,来模拟和实现自定义的手势操做git
touch事件触发时,有3组数据能够得到触摸信息,可能你们会对这几组数据有些混淆,我根据本身的理解来尽可能用通俗的语言给你们解释清楚github
咱们先来看一张图数组
如图所示,咱们在节点B上绑定touch事件,圆圈表明触摸点。浏览器
此时节点B有3个触摸点,即targetTouches
数组有3项,分别储存着触摸点的信息,此时touches
和targetTouches
是相同的。动画
当咱们将手指3移出节点B(始终保持3个手指触摸在屏幕上),那么touchmove事件触发,targetTouches
只剩2项,而touches
依然有3项,此时changedTouches
只有一项(由于只有手指3改变了)。spa
而后咱们让全部手指离开屏幕,那么此时touchend事件触发,touches
只剩0项,targetTouches
剩0项,changedTouches
有3项(由于3个手指发生了变化)。设计
好了,理解这些概念,有助于咱们理解代码中什么时候改去哪一个touch数组里面的值。code
tap能够理解为点击事件,和click不一样的是,移动端的click事件有大约300ms的延迟,这是由于浏览器要判断是否为双击事件。blog
思路
说明:时间差用来判断用户触摸的时长,超过规定时间则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) }
经过以上的思路讲解和代码实现,咱们完成了一个单点触控的移动端手势库,是否是火烧眉毛的想要一睹为快、体验一番。
最后附上本次分享的源码和文档:https://github.com/ansenhuang/axe/blob/master/packages/touch/README.md