其實一直就想花些時間讀一讀那些優秀的開源庫,今天終於下了決定打算死磕下本身,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