html5移动端长按事件填坑历程
背景:
在h5页面,实现长按弹出上拉菜单,我们知道h5没有所谓的长按事件,有些UI组件库封装了长按事件,比如zepto的longtap
在pc端有鼠标事件(mousedown,mousemove,mouseup),在h5有touch触摸事件(touchstart,touchmove,touchend)
不能因为需要一个长按事件就引入一个库,这里我们就基于touch触摸事件来实现。
touch事件群
- touchstart事件:当手指触摸屏幕的时候触发, 一个手指触摸触发一次;
- touchmove事件:当手指在屏幕上滑动的时候触发,滑动一下可能触发多次,是一个持续的过程
- touchend事件: 当手指从屏幕上移开的时候触发;
- touchcancel事件:当触摸点被中断时触发。例如在触摸过程中突然页面alert()一个提示框
手指快速点击一个元素,会经过:touchstart -> touchend –> click
手指长按一个元素,会经过:touchstart -> touchend
手指滑动,会经过:touchstart -> touchmove(n次) -> touchend
综上:
1、click事件发生在touchstart和touchend之后,这也就是我们所说的移动端点击事件一般有300ms的延迟(可用tap事件替代)
2、长按的过程是touchstart之后touchend之前,区分长按还是点击,可以根据时间判断,比如大于500ms是长按,小于500ms是点击
3、如果在点击屏幕的时候手指滑动的话,是不会触发click事件的
由以上结论,我们就可实现长按事件
主要逻辑:
1、在touchstart里定义延迟器,500ms后执行长按逻辑
2、在touchend里取消延迟器,即在touchstart触发的500ms之内,如果手指从屏幕上移开,则不是长按
3、在touchmove里取消延迟器,即如果手指在屏幕上滑动,则不是长按
代码如下:
<div @touchstart="onTouchStart(d,$event)" @touchmove="onTouchMove($event)" @touchend="onTouchEnd">按钮</div> export default { data () { toucheX: 0, toucheY: 0, timeOutEvent: \'\' }, methods: { // 长按弹出上拉操作面板 onTouchStart (data, e) { this.toucheX = e.targetTouches[0].screenX this.toucheY = e.targetTouches[0].screenY // 开启定时器前先清除定时器,防止重复触发 this.timeOutEvent && clearTimeout(this.timeOutEvent)
// 显示上拉面板 this.timeOutEvent = setTimeout(() => {this.showActionSheet = true }, 500) e.preventDefault() // 阻止系统默认事件 }, onTouchMove (e) { const moveX = e.targetTouches[0].screenX const moveY = e.targetTouches[0].screenY // 解决vivo机型,手指没有move,touchmove事件仍然会调用而导致setTimeout被clear if (this.toucheX !== moveX || this.toucheY !== moveY) { // 手指滑动,清除定时器,中断长按逻辑 this.timeOutEvent && clearTimeout(this.timeOutEvent) } }, onTouchEnd () {
// 清除定时器,结束长按逻辑 this.timeOutEvent && clearTimeout(this.timeOutEvent)
// 若手指离开屏幕,时间小于我们设置的长按时间,则为点击事件 }, } }
注意:
1、H5页面长按会触发系统的默认事件,ios和安卓表现不同,如下图。可以使用event.preventDefault()方法阻止后面默认事件的发生
在touchstart中preventDefault,会导致click事件不触发和a链接点击没反应。可以使用tap代替click,但是a标签的话就不太方便了
在touchmove中preventDefault,会阻止浏览器默认滚动
2、在某些手机,比如vivo,没有move,但是touchmove事件仍然会触发
在这种情况,可以根据touch的位置判断手指是否移动,来区分是点击长按或者是滑动
在touchstart事件中记录touch时的x,y的坐标,然后在touchmove中,再判断touch的位置是否和touchstart中的一样的。
注意,查看TouchEvent最好在谷歌模拟手机浏览器中查看,不要在真实手机浏览器查看,本人在真实手机浏览器console和alert都看不到详细的信息,疑惑了半天,发现真实手机浏览器虽然看不到信息,但是可以直接取值(TouchList是类数组对象)
触屏事件的操作信息都存储在TouchEvent类型对象中,此对象属性较多,下面着重介绍下touches、targetTouches与changedTouches
touches[只读]:手指触摸到屏幕上,所有触摸点的集合;
targetTouchs[只读]:手指触摸到DOM元素(绑定事件的dom节点)上的触摸点的集合
changeTouches[只读]:表示自上次触摸事件以来发生改变的(和触摸事件对应的Touch 对象)
对于 touchstart
事件, changedTouches是此次事件中新增加的触点。
对于 touchmove 事件,changedTouches是和上一次事件相比较,发生了变化的触点。
对于touchend事件,changedTouches
是刚从触摸面离开的触点(最后一次离开屏幕的手指的Touch 对象)
下图中有两个div,只对DIV2绑定了touchstart事件
当手指第一次触摸到DIV2时,三个对象表示的都是一样的;
再放下第二根手指和第三根手指同时触摸DIV1和DIV2时,
此时touches对象表示的是第一根手指、第二根手指、第三根手指的信息
此时targetTouches对象表示的是第一根手指和第三根手指的信息,因为绑定touch事件的节点为DIV2
而changedTouches对象表示的是第二根手指和第三根手指的信息,因为第一根手指没有变化
综上:
touchmove时,如果手指从目标元素(绑定事件的dom节点)滑出,targetTouches还会有此触摸点信息
当一个触摸点从目标元素离开,它的信息将从 touches、targetTouches里移除,但是changedTouches会保留此触摸点信息;
当最后一个触摸点离开,touches、targetTouches变成空值,而changedTouches保留着最后一个离开的触摸点信息
扩展:
Touch.screenX:触点相对于屏幕左边沿的X坐标。只读属性。
Touch.screenY:触点相对于屏幕上边沿的Y坐标。只读属性。
Touch.clientX:触点相对于可见视区(visual viewport)左边沿的X坐标。不包括任何滚动偏移。只读属性。
Touch.clientY:触点相对于可见视区(visual viewport)上边沿的Y坐标。不包括任何滚动偏移。只读属性。
Touch.pageX:触点相对于HTML文档左边沿的X坐标。当存在水平滚动的偏移时,这个值包含了水平滚动的偏移。只读属性。
Touch.pageY:触点相对于HTML文档上边沿的Y坐标。当存在水平滚动的偏移时,这个值包含了垂直滚动的偏移。只读属性。