个人接触移动端的经验还是比较欠缺的,结合网上一些博文作了这方面的总结。
300ms 点击延迟
先说这大概是一种什么现象:我们先将点击动作分为两类,一种是单击,另一种则是双击;由于苹果厂的历史原因,移动端的浏览器需要300ms的响应延迟来判断用户动作是属于单击还是双击。由于苹果那个时候还是老大哥(虽然现在也还勉强),它有着双击缩放和滚动的特性,所以我们经常看到以下的这种HTML的头部媒体标签设置:
1 | <meta name="viewport" content="user-scalable=no"> |
作用是禁止缩放,但是这个用度有点过猛,会把所有的缩放特性全部抹掉,除了前面说的双击问题还包括你双指缩放操作,这肯定不是我们所期望的,也影响用户的使用体验,所以可以通过如下的媒体设置,这种方式通过指定浏览器视窗宽度为移动端设备的视窗宽度,按照移动端的等比缩放,即所谓的响应式,当移动设备通过该媒体标签识别出是响应式网站后,就会自动禁用掉前面的双击缩放和延迟问题,并且不会禁止双手的正常缩放操作。
1 | <meta name="viewport" content="width=device-width"> |
点透问题
什么是点透问题呢?在以前没有研究前,我一直以为就是两个容器A和B,A在B在上面,点击A但是B的某些事件监听也被触发了,但是仔细一想,这nm不就是事件冒泡么,肯定不是这么一回事,后面看了几篇相关博文发现点透还是有几个条件的首先A和B不是后代继承关系,其次A在B的层叠流之上,最后也是最为关键的一点:上层的A在点击后消失或者移开覆盖B的区域,B本身有默认(a标签)或者绑定的click
事件。
那么点透的本质是啥呢,根据我的理解:由于在移动端触摸屏幕进行点击动作的时候,其实是有2个事件触发的一个是touch
,另一个则是click
,后者我们已经很熟悉了,主要是前面这个touch
,它会先于click
事件前被触发完成,而click
在移动端前文中已经讲到过有300ms的响应延迟,实际触发顺序是touchstart > touchmove > touchend > click
。在touchstart
时将覆盖在上面的层级处理掉,300ms后下面的层级就会触发click
事件,若下面的这个是一个链接,就会发生跳转。
解决方案:
①在touch
阶段通过e.preventDefault()
来阻止后面的click
触发。
②通过setTimeout
使上层在300ms后再移除。
③使用FastClick库,本质是检测到touchend
时,通过DOM自定义事件立即触发模拟一个click事件,并把浏览器在300ms之后真正的click事件阻止掉。
简单模拟一个FastClick
实践
开始简单模拟前,有几个构建自定义事件的API我们需要学习一下:
Document.createEvent(Event type)
,该API能够协助我们创建自定义的事件,其中Event type
的选取只能是事件模块中定义的值,如UIEvents
、MouseEvents
、MutationEvents
等。具体见Mozilla。
模拟FastClick
的一个关键环节就是自定义MouseEvents
事件,然后使用initMouseEvent
API去初始化事件内容,最后使用dispatchEvent
发布这个事件:
1 | event.initMouseEvent(type, canBubble, cancelable, view, |
阅读FastClick
源码,有几个额外的细节:
① 对屏幕双击的情景,我们需要设置一个监听时长tapDelay
,默认大小为200ms
。如果从第一次触碰结束到第二次触碰开始的间隔小于这个区间,我们可以认为这是一次双击事件,不再走后续的自定义派发。
② 对屏幕长按的情景,我们同样要设置一个监听时长tapTimeout
,默认大小为700ms
。如果从触碰开始到触碰结束的间隔大于该区间,我们认为本次点击是一次长按操作,将不会进行后续的自定义派发。
③ 对触屏移动的情景,我们需要设置一个移动范围去评估手指按下去是在进行移动事件还是点击事件,参考源码,命名boundary
,默认大小为10px
。同时结合event.changedTouches
去获取对应的定位信息加以判断。
④ Android和IOS的事件监听有所差异,IOS只需配置click
、touchstart
、touchmove
、touchend
、touchcancel
;Android除了IOS配置的还需配置mouseover
、mousedown
、mouseup
。
根据以上,我们能够整理出如下的简易版FastClick
:
1 | class SimpleFastClick { |
结论
经过实测发现,其实touch
的默认触发事件就是click
。所以我们在任何一个touch
的事件监听中进行Event.preventDefault()
或return
都能够阻止click
的触发。也基于这个原理,我们可以在touchend
中阻止其后的click
触发,并使用我们自定义的click
派发替代。这么做其实就是规避那300ms
带来的一系列问题,使得我们的事件触发严格有序。