其实一直就想花些时间读一读那些优秀的开源库,今天终于下了决定打算死磕下本身,2016年每月读2-3个优秀的开源库,把源码精彩的地方和本身心得分享给你们。javascript
(一)背景
(二)源码解析
(三)Zepto 点击穿透与 FastClick
(四)新技能 Get
(五)参考文献html
作前端的必定都知道,原生click事件在移动浏览器上会有300毫秒的延迟,会让用户以为卡顿,这300毫秒究竟是怎么来的呢,估计就不太知道了,再继续深究这300毫秒的来源是什么,应该会更少人知道吧。国外有一篇颇有名的文章说的很详细,有兴趣能够看一下:what-exactly-is.....-the-300ms-click-delay。简短来讲:是移动浏览器都支持双击缩放或双击滚动的操做,因为当用户第一次点击屏幕后,浏览器不能马上判断用户确实要打开这个连接,仍是想要进行双击的操做,所以几乎如今全部浏览器都效仿Safari当年的约定,在点击事件上加了300毫秒的延迟。前端
在这个web页面横行的年代,每一个点击事件都有300毫秒的延迟是不容许的。再后来出来了不少的解决办法,好比Zepto的tap事件(会引起击穿的bug,后面会着重说),fastclick.js等均可以解决,可是多多少少会有些负做用,综合起来我最喜欢fastclick的解决方案,今天就来读一读它的源码吧~java
829行
:如今通常插件都会用这种方式兼容AMD、commonJs风格、原生Js的方式。还有CMD等这里没有兼容,这里能够根据本身项目需求稍做修改。android
//优先兼容AMD方式 if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { define(function() { return FastClick; }); } else if (typeof module !== 'undefined' && module.exports) { //兼容commonJs风格 module.exports = FastClick.attach; module.exports.FastClick = FastClick; } else { //最后兼容原生Js window.FastClick = FastClick; }
824行
:FastClick入口方法attachgit
//layer参数:要监听的dom对象,通常是document.body //options参数:用来覆盖自定义参数,我的建议不去覆盖, //由于里面的参数设定都是FastClick的精华, //好比规定了touchstart和touchend事件之间的200毫秒最小间隔。 FastClick.attach = function(layer, options) { return new FastClick(layer, options); };
23-103行
:设置默认值//好比这几个参数,上面提到不建议自定义覆盖, //这些参数正是FastClick的精华所在, //大幅度修改数值可能让整个库的功效大打折扣。 this.touchBoundary = options.touchBoundary || 10; this.tapDelay = options.tapDelay || 200; this.tapTimeout = options.tapTimeout || 700;
105-107行
:判断是否须要调用FastClick官网上when-it-isnt-needed说的很清楚,如下状况不须要FastClick。github
全部pc浏览器web
浏览器不支持ontouchstartchrome
安卓中chrome(all versions)meta中有user-scalable="no"属性segmentfault
安卓中chrome 32+ meta中有width=device-width 属性
BlackBerry 10.3+
Firefox 27+
有-ms-touch-action: manipulation属性的IE10
有touch-action: manipulation属性的IE11
//因此在不须要FastClick的浏览器会直接return掉, //不会执行下面的全部代码。 if (FastClick.notNeeded(layer)) { return; }
110-132行
:自定义函数绑定在对应默认事件上layer.addEventListener('touchstart', this.onTouchStart, false); layer.addEventListener('touchmove', this.onTouchMove, false); layer.addEventListener('touchend', this.onTouchEnd, false); layer.addEventListener('touchcancel', this.onTouchCancel, false);
137-159行
:对旧版本android不支持 stopImmediatePropagation 事件的兼容这里有一个知识点:stopImmediatePropagation和stopPropagation的区别,后面总结会详细说。
164-173行
:兼容直接绑定在dom上的onclick事件//把<body onclick="fun()"></body> 直接绑定在dom上的onclick事件 //改成绑定在该dom上的形式, //为了以后的模拟点击事件。 if (typeof layer.onclick === 'function') { oldOnClick = layer.onclick; layer.addEventListener('click', function(event) { oldOnClick(event); }, false); layer.onclick = null; }
181-219行
:浏览器UA判断311-319行
:determineEventType 兼容安卓chrome中的select框事件从click改成mousedown325-355行
:focus 兼容苹果手机setSelectionRange不能正确获取焦点的bug343-367行
:updateScrollParent (待看)374-382行
:getTargetElementFromEventTarget 兼容获取点击元素,iOS 4.1中会获取文字做为焦点,取它的父元素dom497-512行
:findControl
//点击label的时候,找到他对应的元素,并获取焦点 <label for="input"></label> <input id="input"/>
459-467
:touchHasMoved 手指点击时移动间距大于10px,返回true476-488
:onTouchMove 手指点击时移动间距大于10px,即视为touchmove,不触发模拟click事件
通常状况下用不到,如下方法,特殊需求可能会用到。
227-254行
:needsClick 肯定哪些元素须要原生的click事件 263-285行
:needsFocus 肯定哪些元素须要原生的focus事件
//若是哪些元素须要使用原生的click或者是focus事件,须要在dom上加上class='needsClick' <a class="needsclick">Ignored by FastClick</a>
712-726行
:destroy 这个方法只在源码中,若是有需求销毁事件,重构源码时能够调用这个方法。
391-450
:onTouchStart
FastClick.prototype.onTouchStart = function(event) { //tapDelay默认300毫秒,点击时间差小于300毫秒,则阻止事件再次触发,阻止短期内双击的问题 if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { event.preventDefault(); } }
521-610
:onTouchEnd
if (!this.needsClick(targetElement)) { // 若是这不是一个须要使用原生click的元素,则屏蔽原生事件,避免触发两次click event.preventDefault(); // 触发一次模拟的click this.sendClick(targetElement, event); }
294-309
:sendClick(核心方法)
//这个事件会在onTouchEnd中用到,通过一系列的判断,符合条件,调用这个模拟事件 FastClick.prototype.sendClick = function(targetElement, event) { var clickEvent, touch; //建立一个鼠标事件 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); //触发这个事件 targetElement.dispatchEvent(clickEvent); };
最近项目中在用Zepto的插件touch.js中tap事件,来解决移动浏览器中300毫秒延迟的问题。可是出现了各类击穿现象
同页面tap点击弹出弹层,弹层中也有一个button,正好重叠的时候,会出现击穿
tap事件点击,页面跳转,新页面中同位置也有一个按钮,会出现击穿
咱们能够看下Zepto对 singleTap 事件的处理。见源码 136-143 行,能够看出在 touchend 响应 250ms 无操做后,则触发singleTap。
//trigger single tap after 250ms of inactivity else { touchTimeout = setTimeout(function(){ touchTimeout = null if (touch.el) touch.el.trigger('singleTap') touch = {} }, 250) }
用这篇文章里面的一句话来解释下Zepto的穿透问题也来讲说touch事件与点击穿透问题
zepto中的 tap 经过兼听绑定在 document 上的 touch 事件来完成 tap 事件的模拟的,是经过事件冒泡实现的。在点击完成时(touchstart / touchend)的 tap 事件须要冒泡到 document 上才会触发。而在冒泡到 document 以前,手指接触和离开屏幕(touchstart / touchend)是会触发 click 事件的。
由于 click 事件有延迟(大概是300ms,为了实现safari的双击事件的设计),因此在执行完 tap 事件以后,弹出层立马就隐藏了,此时 click 事件还在延迟的 300ms 之中。当 300ms 到来的时候,click 到的实际上是隐藏元素下方的元素。
若是正下方的元素有绑定 click 事件,此时便会触发,若是没有绑定 click 事件的话就当没发生。若是正下方的是 input 输入框(或是 select / radio / checkbox),点击默认 focus 而弹出输入键盘,也就出现了上面的“点透”现象。
因此到这里,我的仍是建议直接使用fastclick.js库来解决移动端浏览器300毫秒的问题,不建议本身写,坑仍是挺多的,这个库压缩后仍是挺小的,能够用各类方式引用,来替代Zepto中的touch.js插件是个不错的办法。
经过读这个库,发现了不少知识上的盲区或者理解的并非很透彻的点,再深化一下~
stopImmediatePropagation 和 stopPropagation 的区别 参考文章
他们均可以阻止事件冒泡到父元素
stopImmediatePropagation多作了一件事:阻止绑定在该元素上其余事件运行
stopImmediatePropagation 和 stopPropagation 的区别:http://segmentfault.com/q/1010000000120125
也来讲说touch事件与点击穿透问题:http://segmentfault.com/a/1190000003848737