移动端触摸点击事件优化(fastclick源码学习)最近在做一些微信移动端的页面,记录下移动端的学习优化过程这里的touch和click事件,主要内容围绕fastclick展开。fastclickgithub问题的由来是手机浏览器在用户点击屏幕后一般会延迟300ms左右才会触发点击事件——GOOGLE手机打开这个链接可以查看延迟demo(现在很多浏览器都没有延迟问题,详见fastclickgithub,不过笔者手机浏览器还是有300毫秒延迟的问题)为什么下面的截图会有300ms的延迟?主要原因是有双击缩放功能,浏览器需要判断用户的点击是否为双击缩放。如果不解决这个问题,1.用户体验会很差,不流畅,尤其是在密集操作的场景下,比如计算器。300ms的延迟问题不解决,会感觉反应很慢;2.点击通过事件的触发顺序在了解fastclick的思想之前,我们先看一下事件触发顺序将被触发。mouseenter:当指针设备(通常是鼠标)移动到元素上时,将触发mousemove事件。mousedownclickmobileclick有300ms的延迟问题,而touch没有。fastclick的思路fastclick的思路是用touch来模拟tap(触摸)。如果认为是有效的点击,会立即在touchend模拟点击事件,分发给事件源(相当于主动触发点击),同时阻塞。浏览器在300毫秒后产生的点击。源码学习,先看使用示例。很简单,我们的思路一直跟着attach。if('addEventListener'indocument){document.addEventListener('DOMContentLoaded',function(){FastClick.attach(document.body);},false);}直接将fastlick绑定到body--。看源码结构(注:以下所有代码都去掉了一些不影响思路理解的部分,大部分思路写在注释中)//constructorfunctionFastClick(layer,options)//判断是否需要浏览器的原生点击事件(对于一些特殊的元素比如表单)FastClick.prototype.needsClick=function(target)//发送模拟点击事件FastClick.prototype.sendClick=function(targetElement,event)//touchstarteventhandlerFastClick.prototype.onTouchStart=function(event)//touchmoveeventhandlerFastClick.prototype.onTouchMove=function(event)//touchendeventhandlerFastClick.prototype.onTouchEnd=function(event)//判断点击是否有效FastClick.prototype.onMouse=function(event)//点击处理器捕获阶段监听FastClick.prototype.onClick=function(event)//销毁fastlick,移除事件绑定FastClick.prototype.destroy=function()//绑定接口FastClick.attach=function(layer,options){returnnewFastClick(layer,options);};attach其实是执行构造函数初始化的,那我们看看构造函数FastClick(layer,options){//一些属性初始化//有些旧版本的Android浏览器不支持bind,polyfillfunctionbind(方法,context){returnfunction(){returnmethod.apply(context,arguments);};}varmethods=['onMouse','onClick','onTouchStart','onTouchMove','onTouchEnd','onTouchCancel'];变量上下文=这个;//将所有处理程序的this绑定到fastclick实例for(vari=0,l=methods.length;i1){returntrue;}//获取事件的源元素(目标阶段的元素)targetElement=this.getTargetElementFromEventTarget(event.target);touch=event.targetTouches[0];this.trackingClick=true;//标记开始跟踪点击this.trackingClickStart=event.timeStamp;//开始跟踪时间this.targetElement=targetElement;//事件源元素//触摸坐标,然后使用this.touchStartX=touch.pageX;this.touchStartY=touch.pageY;//防止快速双击时出现幻影点击(问题#36)};touchstart主要是初始化trackedtap相关的一些属性,方便后面的判断'下一个touchmove法stClick.prototype.onTouchMove=function(event){if(!this.trackingClick){返回真;}//如果触摸已经移动,取消点击跟踪移动到其他元素bounds,取消这个点击模拟过程,按照原来的过程this.trackingClick=false;this.targetElement=null;}返回真;};touchmove比较简单,主要兼容滑动tap(swiper)等。滑出边界不模拟点击。下面是touchendFastClick.prototype.onTouchEnd=function(event){varforElement,trackingClickStart,targetTagName,scrollParent,touch,targetElement=this.targetElement;如果(!this.trackingClick){返回真;}//防止快速双击出现幻影点击(问题#36)//防止快速双击if((event.timeStamp-this.lastClickTime)this.tapTimeout){返回真;}this.lastClickTime=event.timeStamp;this.trackingClick=false;this.trackingClickStart=0;//防止实际点击通过-除非目标节点被标记为需要//真正的点击或者如果它在白名单中,在这种情况下只允许非程序点击。if(!this.needsClick(targetElement)){event.preventDefault();//停止之后的clickthis.sendClick(targetElement,event);//发送模拟click}returnfalse;};//发送模拟的点击事件FastClick.prototype.sendClick=function(targetElement,event){varclickEvent,touch;//在某些Android设备上,activeElement需要模糊,否则合成点击将无效(#24)}touch=event.changedTouches[0];//模拟click//合成一个click事件,带有一个extr一个属性,因此可以对其进行跟踪clickEvent=document.createEvent('MouseEvents');clickEvent.initMouseEvent(this.determineEventType(targetElement),true,true,window,1,touch.screenX,touch.screenY,touch.clientX,touch.clientY,false,false,false,false,0,null);clickEvent.forwardedTouchEvent=true;//将模拟点击分发给targetElementtargetElement.dispatchEvent(clickEvent);};最后还会在层的点击捕获阶段监听//clickhandlercapturephase监听FastClick.prototype.onClick=function(event){varpermitted;//另一个使用第三方代码提供的类FastClick库可能会在FastClick触发点击事件之前触发点击事件(问题#44)。在这种情况下,将点击跟踪标志设置回false并提前返回。这将导致onTouchEnd提前返回。if(this.trackingClick){//1,越界会被设置为false,2成功模拟了一次点击并防止点击被设置为false。3、避免三方库的影响。this.targetElement=null;this.trackingClick=false;返回真;}//很奇怪iOS上的行为(问题#18):如果提交元素存在于表单中并且用户在iOS模拟器中点击回车键或点击弹出式操作系统键盘上的Go按钮,则将出现一种“假”点击事件以提交类型的输入元素作为目标触发。if(event.target.type==='submit'&&event.detail===0){returntrue;}permitted=this.onMouse(event);//只有在不允许点击的情况下才取消设置targetElement。这将确保onMouse中对!targetElement的检查失败并且浏览器的点击不会通过。if(!permitted){this.targetElement=null;}//如果允许点击,则返回true以完成操作。允许退货;};//判断这次鼠标标记是否有效FastClick.prototype.onMouse=function(event){//如果目标元素从未设置(因为从未触发触摸事件)允许事件if(!this.targetElement){returntrue;}//标记fastclick模拟生成的事件if(event.forwardedTouchEvent){returntrue;}//以编程方式生成的针对特定元素的事件应该被允许if(!event.cancelable){returntrue;}//导出并检查目标元素,看是否需要允许鼠标事件;//除非明确启用,否则防止非触摸点击事件触发操作,//以防止重影/双击。//是否需要原始的点击if(!this.needsClick(this.targetElement)||this.cancelNextClick){//防止在FastClick元素上声明的任何用户添加的侦听器被触发。如果(event.stopImmediatePropagation){event.stopImmediatePropagation();}else{//针对不支持Event#stopImmediatePropagation的浏览器(例如Android2)的部分hackevent.propagationStopped=true;}//取消事件阻止事件捕获和冒泡event.stopPropagation();事件.preventDefault();返回假;}//如果允许鼠标事件,则返回true以完成操作。返回真;};这里主要是判断点击是否有效(如果无效,防止捕获冒泡)至此基本流程有一点需要注意,笔者在chrome中测试过stopPropagation(版本64.0.3282.119(正式版))(64-bit)),stopImmediatePropagation不仅会防止冒泡还会阻止捕获过程。最后,推荐阅读源码。fastclickgithub上有很多关于focus、兼容不同浏览器、特殊表单元素的源码。这里是作者的代码中文注释代码,有中文注释。如有错误,欢迎批评指正。参考MDNhttps://juejin.im/entry/55d73...