资讯专栏INFORMATION COLUMN

移动端触摸、点击事件优化(fastclick源码学习)

paney129 / 725人阅读

摘要:移动端触摸点击事件优化源码学习最近在做一些微信移动端的页面,在此记录关于移动端触摸和点击事件的学习优化过程,主要内容围绕展开。当指针设备通常指鼠标在元素上移动时事件被触发。移动端有延迟问题,可没有哦。

移动端触摸、点击事件优化(fastclick源码学习)

最近在做一些微信移动端的页面,在此记录关于移动端触摸和点击事件的学习优化过程,主要内容围绕fastclick展开。
fastclick github

问题起源
移动端浏览器一般在用户点击屏幕之后会延迟大约300ms才触发click event
——GOOGLE

手机打开此链接查看延迟demo
(现在许多浏览器已经不存在延迟问题了,详见fastclick github,但笔者的手机浏览器还是出现了三百毫秒延迟的问题)
截图如下

为什么会300ms延迟呢,主要是有一个双击缩放功能,浏览器需要判断用户点击是否为双击缩放。这个问题不解决,
1、用户体验就会很差,很不流畅,尤其是在密集操作场景下,比如计算器,不解决300ms延迟问题,感觉反应很慢;
2、点击穿透问题

事件触发顺序

在了解fastclick的思路之前,我们先看一下事件触发顺序是怎样的

touchstart

touchmove

touchend

mouseover :当指针设备移动到存在监听器的元素或其子元素的时候,mouseover事件就会被触发。

mouseenter:当指针设备( 通常指鼠标 )在元素上移动时, mousemove 事件被触发。

mousedown

click

移动端click有300ms延迟问题,touch可没有哦。

fastclick思路

fastclick的思路就是利用touch来模拟tap(触碰),如果认为是一次有效的tap,则在touchend时立即模拟一个click事件,分发到事件源(相当于主动触发一次click),同时阻止掉浏览器300ms后产生的click。

源码学习

先看使用示例,很简单,我们的思路就一直跟着attach走。

if ("addEventListener" in document) {
    document.addEventListener("DOMContentLoaded", function() {
        FastClick.attach(document.body);
    }, false);
}

直接给body绑定fastlick就行了- -。
看源代码结构(注:以下所有代码均去掉了一些不影响理解思路的部分,大部分思路写在注释中)

//构造函数
    function FastClick(layer, options)
//判断是否需要浏览器原生的click事件(针对一些特殊元素比如表单)
    FastClick.prototype.needsClick = function(target)
//发送模拟的click event
    FastClick.prototype.sendClick = function(targetElement, event)
// touchstart eventhandler
    FastClick.prototype.onTouchStart = function(event)
// touchmove eventhandler
    FastClick.prototype.onTouchMove = function(event)
// touchend eventhandler
    FastClick.prototype.onTouchEnd = function(event)
// 判断这次tap是否有效
    FastClick.prototype.onMouse = function(event) 
//click handler 捕获阶段监听
    FastClick.prototype.onClick = function(event)
//销毁fastlick,移除事件绑定
    FastClick.prototype.destroy = function()
//绑定接口
    FastClick.attach = function(layer, options) {
        return new FastClick(layer, options);
    };

attach实际就执行了构造函数进行初始化,接下来我们来看构造函数发生了什么

    function FastClick(layer,options){
        //一些属性初始化
        //安卓一些老版本浏览器不支持bind, poly fill
        function bind (method, context) {
          return function () {
            return method.apply(context, arguments);
          };
        }
        var methods = ["onMouse", "onClick", "onTouchStart", "onTouchMove", 
        "onTouchEnd", "onTouchCancel"];
        var context = this;
        //将所有handler的this绑定到fastclick实例
        for (var i = 0, l = methods.length; i < l; i++) {
            context[methods[i]] = bind(context[methods[i]], context);
        }
        //为当前fast click对象绑定的layer(我们的示例中时document.body)加监听
        layer.addEventListener("click", this.onClick, true);//true 捕获阶段触发 
        layer.addEventListener("touchstart", this.onTouchStart, false);
        layer.addEventListener("touchmove", this.onTouchMove, false);
        layer.addEventListener("touchend", this.onTouchEnd, false);
        layer.addEventListener("touchcancel", this.onTouchCancel, false);
    }

构造函数主要是初始化一些属性,polyfill,和添加监听,
下面开始看一下重头戏,touchstart,touchend是如何判断tap是否有效、如何模拟click事件、如何阻止300ms后的click
touchstart

  FastClick.prototype.onTouchStart = function (event) {
    var targetElement, touch, selection;

    // Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
    // 如果多触点可能是在缩放,不对targetElement初始化,在此提前终止避免误模拟产生click
    if (event.targetTouches.length > 1) {
      return true;
    }

    //获取发生事件源元素(目标阶段的元素)
    targetElement = this.getTargetElementFromEventTarget(event.target);
    touch = event.targetTouches[0];
    
    this.trackingClick = true;//标记开始跟踪click
    this.trackingClickStart = event.timeStamp;//开始跟踪时间
    this.targetElement = targetElement;//事件源元素

    //触摸坐标,接下来判断是否越界用到
    this.touchStartX = touch.pageX;
    this.touchStartY = touch.pageY;

    // Prevent phantom clicks on fast double-tap (issue #36)
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
      event.preventDefault();//阻止之后的click
    }

    return true;
  };

touchstart主要是初始化跟踪的tap相关的一些属性,用于之后的判断‘
接下来touchmove

 FastClick.prototype.onTouchMove = function (event) {
    if (!this.trackingClick) {
      return true;
    }

    // If the touch has moved, cancel the click tracking 移动到了其他元素
    if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {//移动越界了,取消本次click模拟处理,走原生流程
      this.trackingClick = false;
      this.targetElement = null;
    }

    return true;
  };

touchmove比较简单,主要是兼容滑动tap(swiper)等等,滑动越界则不模拟click
下面是touchend

FastClick.prototype.onTouchEnd = function (event) {
    var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;

    if (!this.trackingClick) {
      return true;
    }

    // Prevent phantom clicks on fast double-tap (issue #36)
    //阻止快速双击
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
      this.cancelNextClick = true;
      return true;
    }
    //超时就不算click了,走原生流程,不阻止click
    if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
      return true;
    }

    this.lastClickTime = event.timeStamp;

    this.trackingClick = false;
    this.trackingClickStart = 0;



    // Prevent the actual click from going though - unless the target node is marked as requiring
    // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted.
    if (!this.needsClick(targetElement)) {
      event.preventDefault();//阻止之后的click
      this.sendClick(targetElement, event);//发送模拟click
    }

    return false;
  };
 //发送模拟的click event
  FastClick.prototype.sendClick = function (targetElement, event) {
    var clickEvent, touch;

    // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
    if (document.activeElement && document.activeElement !== targetElement) {
      document.activeElement.blur();
    }

    touch = event.changedTouches[0];

    //模拟click
    // Synthesise a click event, with an extra attribute so it can be tracked
    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;
    //向targetElement分发模拟的click
    targetElement.dispatchEvent(clickEvent);
  };

最后,还会在layer的click捕获阶段监听

  //click handler 捕获阶段监听
  FastClick.prototype.onClick = function (event) {
    var permitted;
    // It"s possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early.
    if (this.trackingClick) {//1、出界会置为false,2成功模拟了一次完成tap并阻止click也会置为false,3、避免三方库影响
      this.targetElement = null;
      this.trackingClick = false;
      return true;
    }

    // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of "fake" click event will be triggered with the submit-type input element as the target.
    if (event.target.type === "submit" && event.detail === 0) {
      return true;
    }

    permitted = this.onMouse(event);

    // Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser"s click doesn"t go through.
    if (!permitted) {
      this.targetElement = null;
    }

    // If clicks are permitted, return true for the action to go through.
    return permitted;
  };

 // 判断这次鼠标是否有效
  FastClick.prototype.onMouse = function (event) {

    // If a target element was never set (because a touch event was never fired) allow the event
    if (!this.targetElement) {
      return true;
    }

    // 标记fastclick模拟产生的event
    if (event.forwardedTouchEvent) {
      return true;
    }

    // Programmatically generated events targeting a specific element should be permitted
    if (!event.cancelable) {
      return true;
    }

    // Derive and check the target element to see whether the mouse event needs to be permitted;
    // unless explicitly enabled, prevent non-touch click events from triggering actions,
    // to prevent ghost/doubleclicks.
    // 是否需要原生的click
    if (!this.needsClick(this.targetElement) || this.cancelNextClick) {

      // Prevent any user-added listeners declared on FastClick element from being fired.
      if (event.stopImmediatePropagation) {
        event.stopImmediatePropagation();
      } else {

        // Part of the hack for browsers that don"t support Event#stopImmediatePropagation (e.g. Android 2)
        event.propagationStopped = true;
      }

      // Cancel the event 阻止事件捕获和冒泡
      event.stopPropagation();
      event.preventDefault();

      return false;
    }

    // If the mouse event is permitted, return true for the action to go through.
    return true;
  };

这里主要是判断这次click是否有效(如无效,则阻止捕获和冒泡)
至此基本流程已经结束。
其中有1个注意的点,笔者在chrome(Version 64.0.3282.119 (Official Build) (64-bit))已测试
stopPropagation,stopImmediatePropagation不仅会阻止冒泡还会阻止捕获过程哦。

最后

推荐阅读源码,源码中有许多关于focus、不同浏览器兼容和特殊表单元素的处理fastclick github。
这里是笔者带有中文注释的代码中文注释代码。
如有纰漏,欢迎批评指正。

Reference

MDN
https://juejin.im/entry/55d73...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107935.html

相关文章

  • 移动触摸点击事件优化fastclick源码学习

    摘要:移动端触摸点击事件优化源码学习最近在做一些微信移动端的页面,在此记录关于移动端触摸和点击事件的学习优化过程,主要内容围绕展开。当指针设备通常指鼠标在元素上移动时事件被触发。移动端有延迟问题,可没有哦。 移动端触摸、点击事件优化(fastclick源码学习) 最近在做一些微信移动端的页面,在此记录关于移动端触摸和点击事件的学习优化过程,主要内容围绕fastclick展开。fastclic...

    fxp 评论0 收藏0
  • 移动使用swiper+iscroll+fastClick:安卓触摸swiper会触发点击事件

    摘要:难道是安卓上和执行顺序异于其他浏览器。因为使用了以后事件变得极其敏感,所有的事件触发之前,都会触发。按照的逻辑,一旦触发之后,所有的都被阻止冒泡,就会出现上面说的问题,解决方案如下图增加上图这个判定的即可。 这两天做H5页面,使用swiper+iscroll+fastClick,并没有用swiper提供的tap和click事件,自己在元素上bind,因为回调函数是统一处理,就没用swi...

    KoreyLee 评论0 收藏0
  • zepto touch 库源码分析

    摘要:源码分析不愿意下代码的可以直接点这里地址首先赞一下的代码注释,非常全。属性一个对象,包含了代表所有从上一次触摸事件到此次事件过程中,状态发生了改变的触点的对象。 所谓 zepto 的 touch 其实就是指这个文件啦,可以看到区区 165 行(包括注释)就完成了 swipe 和 tap 相关的事件实现。在正式开始分析源码之前,我们先说说 touch 相关的几个事件,因为无论是 tap ...

    lentrue 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<