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时,三个对象表示的都是一样的

   再放下第二根手指和第三根手指同时触摸DIV1DIV2时,

        此时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坐标。当存在水平滚动的偏移时,这个值包含了垂直滚动的偏移。只读属性。

posted @ 2021-01-15 17:12  曹小半仙  阅读(8699)  评论(0编辑  收藏  举报