fastclick源码简析

fastclick是什么,它解决了什么问题呢?fastclick解决了移动端点击的约300ms延迟问题。当触发click事件时,无法判断用户是想进行双击还是单击,所以有个约300ms的判断是否会进行第二次点击操作。

基本结构

function FastClick(layer, options) {}
// ...... FastClick.attach = function(layer, options) { return new FastClick(layer, options); }; // 这个方法用于判断是否需要fastclick,因为如果用户设置了如禁止播放的功能, // 那么默认将不需要fastClick, 这个方法代码长,这里不贴,里面是关于不同版本 // 浏览器判断和对user-scalable的获取判断 FastClick.notNeeded = function(layer) { // ... }

可以看到基本结构很简单,定义一个构造函数,并且挂载一个静态函数attach(js没有面向对象语法,但是如果在构造函数挂载函数,实际上等同于面向对象的Class.staticMethod,即不需要实例即可访问的函数,同理,原型上的方法是需要创建实例才能使用)。
attach方法就是创建一个FastClick实例,所以基本上,fastClick的操作就在构造函数初始化内完成。接下来继续看里面的内容。

对象结构

attach方法已经拜读,可以先忽略了。

// 对象属性
function FastClick(layer, options) { this.trackingClick = false; // 当touchStart时候置为true this.trackingClickStart = 0; // 当touchStart时候的时间错 this.targetElement = null; // 触发的元素 this.touchStartX = 0; // touch位置x轴 this.touchStartY = 0; // touch位置y轴 this.lastTouchIdentifier = 0; // 注释:最后触摸的id,取自Touch.identifier. this.touchBoundary = options.touchBoundary || 10; // move边界 this.layer = layer; // 挂载的dom元素,该dom元素下fastclick将生效 this.tapDelay = options.tapDelay || 200; this.tapTimeout = options.tapTimeout || 700; // ... 执行过程 }

构造函数执行过程

function FastClick(layer, options) {
  // ...属性已省略 var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel']; // 列举需要挂载实例中回到到layer上的事件 var context = this; for (var i = 0, l = methods.length; i < l; i++) { context[methods[i]] = bind(context[methods[i]], context); // 绑定fastclick对象的上下文给回调函数, 这些回调是定义在构造函数的prototype上,后面会看到 } // Set up event handlers as required 按要求挂载处理事件 if (deviceIsAndroid) { layer.addEventListener('mouseover', this.onMouse, true); layer.addEventListener('mousedown', this.onMouse, true); layer.addEventListener('mouseup', this.onMouse, true); } layer.addEventListener('click', this.onClick, 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); // ... }

构造函数核心过程如图,它会给layer挂载需要处理的回调事件,layer作为上层节点,能够获取到target节点所发生的事件,至于在各种事件中如何处理这个问题,我们会在后续的代码中看到。

bind方法的实现如下, 简单看下,没啥说的,比较简单:

// Some old versions of Android don't have Function.prototype.bind
function bind(method, context) { return function() { return method.apply(context, arguments); }; }

设计思路

关于设计思路,就是说如果是我们模拟这样一个事件,我们需要怎么去判断为点击事件,我想大概可以从几个点去考虑。

  1. 一个点击事件,它必须包含一个点,一个起的动作(start, end)。
  2. 一个点击事件,它必须是迅速的,也就是说,如果你一直按着不动,那么这不是一个点击事件,而是一个长按事件,所以我们还需要一个时间区间,对于在这个时间区间范围内我们才认为是点击事件。所以我们看fastclick的源码属性中,tapDelay就是这样的作用,默认是200ms,可以人为配置。
  3. 一个点击事件,它必须是短距的,也就是说,如果你移动了一个比较长的距离,那么我们认定为是一个移动事件,这也不算是点击事件,可以在touchMove里面去判断。

Fastclick的原型方法

原型方法即实例方法

FastClick.prototype.focus = function(targetEle) { // ... }; FastClick.prototype.updateScrollParent = function(targetEle) { // .. }; FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) { // ... }; FastClick.prototype.touchHasMoved = function(event) { // ... }; FastClick.prototype.findControl = function(event) { // ... }; FastClick.prototype.onTouch = function(event) { // ... }; FastClick.prototype.destroy = function(event) { // ... }; FastClick.prototype.onTouchStart = function(event) { // ... }; FastClick.prototype.onTouchMove = function(event) { // ... }; FastClick.prototype.onMouse = function(event) { // ... }; FastClick.prototype.onClick = function(event) { // ... }; FastClick.prototype.onTouchCancel = function(event) { // ... };

首先,按照我们预想的思路, 应该想看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). if (event.targetTouches.length > 1) { return true; } targetElement = this.getTargetElementFromEventTarget(event.target); touch = event.targetTouches[0]; if (deviceIsIOS) { // ... 此省略部分issue兼容代码 } this.trackingClick = true; 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(); } return true; }

touchStart方法主要存储几个状态属性, trackingClick用于其他事件里面追逐,是否触发click以这个属性为准,trackingClickStart 用于存储点下去时候的时间戳, touchStartX, touchStartY分别为点击事件的X, Y轴。至于其他工作,比如忽略多指操作(即不追踪)。

FastClick.prototype.onTouchHove = function(event) { // trackingClick是是否追踪,不追踪直接返回 if (!this.trackingClick) { return true; } // If the touch has moved, cancel the click tracking if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) { this.trackingClick = false; this.targetElement = null; } return true; }

这是touchMove的处理事件,主要就是两个判断,一个是如果move事件触发元素和touchStart的元素不是一个元素,即已经有移动,则不触发,touchHasMoved方法必然是用于算移动距离是否已经超过限制,超过则不追踪click。
touchHasMoved实现如下:

FastClick.prototype.touchHasMoved = function(event) { var touch = event.changedTouches[0], boundary = this.touchBoundary; if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) { return true; } return false; };

这里有个值得了解的e.changedTouches属性, 属于touch event属性,学习了。

接下来就是最后一步,touchEnd,按我们预计,最后应该是模拟click事件。我看了下,整个end事件处理很多东西,但是最终其实它只做一件事情,关于其兼容判断,我暂时删除。删除完的代码如下:

FastClick.prototype.onTouchEnd = function (event) { if (!this.trackingClick) { return true; } // 模拟click事件 this.sendClick(targetElement, event); }

到这里流程其实已经结束了,最后我们还可以看下如何去模拟click实现,关于sendClick,fastClick是这样实现的:

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]; // 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.dispatchEvent(clickEvent); }; FastClick.prototype.determineEventType = function(targetElement) { //Issue #159: Android Chrome Select Box does not open with a synthetic click event if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') { return 'mousedown'; } return 'click'; }; 

实际上,就是通过DOM接口的createEvent创建一个模拟事件,叫做click覆盖默认的click,这个事件依然会冒泡,依然可以委托。这就是整个fastclick的思路,到这里,就结束了。但是fastclick还有很多兼容性工作,感兴趣的可以移步到fastclick仓库拜读。

转载于:https://www.cnblogs.com/wuweixin/p/11393082.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值