FastClick 源碼解讀

jopen 8年前發布 | 21K 次閱讀 Zepto.js JavaScript開發

其實一直就想花些時間讀一讀那些優秀的開源庫,今天終于下了決定打算死磕下自己,2016年每個月讀2-3個優秀的開源庫,把源碼精彩的地方和自己心得分享給大家。

目錄

(一)背景

(二)源碼解析

(三)Zepto 點擊穿透與 FastClick

(四)新技能 Get

(五)參考文獻

(一)背景

做前端的一定都知道,原生click事件在移動瀏覽器上會有300毫秒的延遲,會讓用戶覺得卡頓,這300毫秒到底是怎么來的呢,估計就不太知道了,再繼續深究這300毫秒的來源是什么,應該會更少人知道吧。國外有一篇很有名的文章說的很詳細,有興趣可以看一下: what-exactly-is.....-the-300ms-click-delay 。簡短來說:是移動瀏覽器都支持雙擊縮放或雙擊滾動的操作,由于當用戶第一次點擊屏幕后,瀏覽器不能立刻判斷用戶確實要打開這個鏈接,還是想要進行雙擊的操作,因此幾乎現在所有瀏覽器都效仿Safari當年的約定,在點擊事件上加了300毫秒的延遲。

在這個web頁面橫行的年代,每個點擊事件都有300毫秒的延遲是不允許的。再后來出來了很多的解決辦法,比如Zepto的tap事件(會引發擊穿的bug,后面會著重說),fastclick.js等都可以解決,但是多多少少會有些負作用,綜合起來我最喜歡 fastclick 的解決方案,今天就來讀一讀它的源碼吧~

(二)解析

1. 引入 FastClick 到自己的環境

829行 :現在一般插件都會用這種方式兼容AMD、commonJs風格、原生Js的方式。還有CMD等這里沒有兼容,這里可以根據自己項目需求稍作修改。

//優先兼容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;
}    

2. 入口

824行 :FastClick入口方法attach

//layer參數:要監聽的dom對象,一般是document.body
//options參數:用來覆蓋自定義參數,個人建議不去覆蓋,
//因為里面的參數設定都是FastClick的精華,
//比如規定了touchstart和touchend事件之間的200毫秒最小間隔。
FastClick.attach = function(layer, options) {
  return new FastClick(layer, options);
};    

3. FastClick 函數

1. 23-103行 :設置默認值

//比如這幾個參數,上面提到不建議自定義覆蓋,
//這些參數正是FastClick的精華所在,
//大幅度修改數值可能讓整個庫的功效大打折扣。
this.touchBoundary = options.touchBoundary || 10;
this.tapDelay = options.tapDelay || 200;
this.tapTimeout = options.tapTimeout || 700;

2. 105-107行 :判斷是否需要調用FastClick

官網上 when-it-isnt-needed 說的很清楚,以下情況不需要FastClick。

  1. 所有pc瀏覽器

  2. 瀏覽器不支持ontouchstart

  3. 安卓中chrome(all versions)meta中有user-scalable="no"屬性

  4. 安卓中chrome 32+ meta中有width=device-width 屬性

  5. BlackBerry 10.3+

  6. Firefox 27+

  7. 有-ms-touch-action: manipulation屬性的IE10

  8. 有touch-action: manipulation屬性的IE11

//所以在不需要FastClick的瀏覽器會直接return掉,
//不會執行下面的所有代碼。
if (FastClick.notNeeded(layer)) {
  return;
}

3. 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);

4. 137-159行 :對舊版本android不支持 stopImmediatePropagation 事件的兼容

這里有一個知識點: stopImmediatePropagation和stopPropagation的區別 ,后面總結會詳細說。

5. 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;
}

4. 兼容 & 判斷

181-219行 :瀏覽器UA判斷

311-319行 :determineEventType 兼容安卓chrome中的select框事件從click改為mousedown

325-355行 :focus 兼容蘋果手機setSelectionRange不能正確獲取焦點的bug

343-367行 :updateScrollParent (待看)

374-382行 :getTargetElementFromEventTarget 兼容獲取點擊元素,iOS 4.1中會獲取文字作為焦點,取它的父元素dom

497-512行 :findControl

//點擊label的時候,找到他對應的元素,并獲取焦點
<label for="input"></label>
<input id="input"/>   

459-467 :touchHasMoved 手指點擊時移動間距大于10px,返回true

476-488 :onTouchMove 手指點擊時移動間距大于10px,即視為touchmove,不觸發模擬click事件

5. 進階方法

一般情況下用不到,以下方法,特殊需求可能會用到。

227-254行 :needsClick 確定哪些元素需要原生的click事件

263-285行 :needsFocus 確定哪些元素需要原生的focus事件

//如果哪些元素需要使用原生的click或者是focus事件,需要在dom上加上class='needsClick'
<a class="needsclick">Ignored by FastClick</a>

712-726行 :destroy 這個方法只在源碼中,如果有需求銷毀事件,重構源碼時可以調用這個方法。

6. 核心方法

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 點擊穿透與 FastClick

最近項目中在用Zepto的插件touch.js中tap事件,來解決移動瀏覽器中300毫秒延遲的問題。但是出現了各種擊穿現象

  1. 同頁面tap點擊彈出彈層,彈層中也有一個button,正好重疊的時候,會出現擊穿

  2. 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事件與點擊穿透問題

  1. zepto中的 tap 通過兼聽綁定在 document 上的 touch 事件來完成 tap 事件的模擬的,是通過事件冒泡實現的。在點擊完成時(touchstart / touchend)的 tap 事件需要冒泡到 document 上才會觸發。而在冒泡到 document 之前,手指接觸和離開屏幕(touchstart / touchend)是會觸發 click 事件的。

  2. 因為 click 事件有延遲(大概是300ms,為了實現safari的雙擊事件的設計),所以在執行完 tap 事件之后,彈出層立馬就隱藏了,此時 click 事件還在延遲的 300ms 之中。當 300ms 到來的時候,click 到的其實是隱藏元素下方的元素。

  3. 如果正下方的元素有綁定 click 事件,此時便會觸發,如果沒有綁定 click 事件的話就當沒發生。如果正下方的是 input 輸入框(或是 select / radio / checkbox),點擊默認 focus 而彈出輸入鍵盤,也就出現了上面的“點透”現象。

所以到這里,個人還是建議直接使用fastclick.js庫來解決移動端瀏覽器300毫秒的問題,不建議自己寫,坑還是挺多的,這個庫壓縮后還是挺小的,可以用各種方式引用,來替代Zepto中的touch.js插件是個不錯的辦法。

(四)新技能 Get

通過讀這個庫,發現了很多知識上的盲區或者理解的并不是很透徹的點,再深化一下~

  1. stopImmediatePropagation 和 stopPropagation 的區別參考文章

    1. 他們都可以阻止事件冒泡到父元素

    2. stopImmediatePropagation多做了一件事:阻止綁定在該元素上其他事件運行

(五)參考文獻

  1. 300毫秒的起源: what-exactly-is.....-the-300ms-click-delay

  2. stopImmediatePropagation 和 stopPropagation 的區別: http://segmentfault.com/q/1010000000120125

  3. 也來說說touch事件與點擊穿透問題: http://segmentfault.com/a/1190000003848737

來自: http://segmentfault.com/a/1190000004295106

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!