fastclick 源码阅读备份
生活随笔
收集整理的這篇文章主要介紹了
fastclick 源码阅读备份
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
;(function () {'use strict';//構造函數function FastClick(layer, options) {var oldOnClick;options = options || {};/*** Whether a click is currently being tracked.** @type boolean*/this.trackingClick = false;/*** Timestamp for when click tracking started.** @type number*/this.trackingClickStart = 0;/*** The element being tracked for a click.** @type EventTarget*/this.targetElement = null;/*** X-coordinate of touch start event.** @type number*/this.touchStartX = 0;/*** Y-coordinate of touch start event.** @type number*/this.touchStartY = 0;//主要hack iOS4下的一個怪異問題this.lastTouchIdentifier = 0;/*** 用于區分是click還是Touchmove,若出點移動超過該值則視為touchmove*/this.touchBoundary = options.touchBoundary || 10;/*** 綁定了FastClick的元素,常規是body*/this.layer = layer;/*** The minimum time between tap(touchstart and touchend) events** @type number*/this.tapDelay = options.tapDelay || 200;/*** The maximum time for a tap** @type number*/this.tapTimeout = options.tapTimeout || 700;//如果是屬于不需要處理的元素類型,則直接返回if (FastClick.notNeeded(layer)) {return;}//語法糖,兼容一些用不了 Function.prototype.bind 的舊安卓//所以后面不走 layer.addEventListener('click', this.onClick.bind(this), true);function bind(method, context) {return function() { return method.apply(context, arguments); };}var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];var context = this;for (var i = 0, l = methods.length; i < l; i++) {context[methods[i]] = bind(context[methods[i]], context);}//安卓則做額外處理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);// 兼容不支持 stopImmediatePropagation 的瀏覽器(比如 Android 2)if (!Event.prototype.stopImmediatePropagation) {layer.removeEventListener = function(type, callback, capture) {var rmv = Node.prototype.removeEventListener;if (type === 'click') {rmv.call(layer, type, callback.hijacked || callback, capture);} else {rmv.call(layer, type, callback, capture);}};layer.addEventListener = function(type, callback, capture) {var adv = Node.prototype.addEventListener;if (type === 'click') {//留意這里 callback.hijacked 中會判斷 event.propagationStopped 是否為真來確保(安卓的onMouse事件)只執行一次//在 onMouse 事件里會給 event.propagationStopped 賦值 trueadv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {if (!event.propagationStopped) {callback(event);}}), capture);} else {adv.call(layer, type, callback, capture);}};}// 如果layer直接在DOM上寫了 onclick 方法,那我們需要把它替換為 addEventListener 綁定形式if (typeof layer.onclick === 'function') {oldOnClick = layer.onclick;layer.addEventListener('click', function(event) {oldOnClick(event);}, false);layer.onclick = null;}}/*** Windows Phone 8.1 fakes user agent string to look like Android and iPhone.** @type boolean*/var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0;/*** Android requires exceptions.** @type boolean*/var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0 && !deviceIsWindowsPhone;/*** iOS requires exceptions.** @type boolean*/var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone;/*** iOS 4 requires an exception for select elements.** @type boolean*/var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);/*** iOS 6.0-7.* requires the target element to be manually derived** @type boolean*/var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS [6-7]_\d/).test(navigator.userAgent);/*** BlackBerry requires exceptions.** @type boolean*/var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0;//判斷元素是否要保留穿透功能FastClick.prototype.needsClick = function(target) {switch (target.nodeName.toLowerCase()) {// disabled的inputcase 'button':case 'select':case 'textarea':if (target.disabled) {return true;}break;case 'input':// file組件必須通過原生click事件點擊才有效if ((deviceIsIOS && target.type === 'file') || target.disabled) {return true;}break;case 'label':case 'iframe':case 'video':return true;}//元素帶了名為“bneedsclick”的class也返回truereturn (/\bneedsclick\b/).test(target.className);};//判斷給定元素是否需要通過合成click事件來模擬聚焦FastClick.prototype.needsFocus = function(target) {switch (target.nodeName.toLowerCase()) {case 'textarea':return true;case 'select':return !deviceIsAndroid; //iOS下的select得走穿透點擊才行case 'input':switch (target.type) {case 'button':case 'checkbox':case 'file':case 'image':case 'radio':case 'submit':return false;}return !target.disabled && !target.readOnly;default://帶有名為“bneedsfocus”的class則返回truereturn (/\bneedsfocus\b/).test(target.className);}};//合成一個click事件并在指定元素上觸發FastClick.prototype.sendClick = function(targetElement, event) {var clickEvent, touch;// 在一些安卓機器中,得讓頁面所存在的 activeElement(聚焦的元素,比如input)失焦,否則合成的click事件將無效if (document.activeElement && document.activeElement !== targetElement) {document.activeElement.blur();}touch = event.changedTouches[0];// 合成(Synthesise) 一個 click 事件// 通過一個額外屬性確保它能被追蹤(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; // fastclick的內部變量,用來識別click事件是原生還是合成的targetElement.dispatchEvent(clickEvent); //立即觸發其click事件
};FastClick.prototype.determineEventType = function(targetElement) {//安卓設備下 Select 無法通過合成的 click 事件被展開,得改為 mousedownif (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {return 'mousedown';}return 'click';};//設置元素聚焦事件FastClick.prototype.focus = function(targetElement) {var length;// 組件建議通過setSelectionRange(selectionStart, selectionEnd)來設定光標范圍(注意這樣還沒有聚焦// 要等到后面觸發 sendClick 事件才會聚焦)// 另外 iOS7 下有些input元素(比如 date datetime month) 的 selectionStart 和 selectionEnd 特性是沒有整型值的,// 導致會拋出一個關于 setSelectionRange 的模糊錯誤,它們需要改用 focus 事件觸發if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') {length = targetElement.value.length;targetElement.setSelectionRange(length, length);} else {//直接觸發其focus事件
targetElement.focus();}};/*** 檢查target是否一個滾動容器里的子元素,如果是則給它加個標記*/FastClick.prototype.updateScrollParent = function(targetElement) {var scrollParent, parentElement;scrollParent = targetElement.fastClickScrollParent;// Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the// target element was moved to another parent.if (!scrollParent || !scrollParent.contains(targetElement)) {parentElement = targetElement;do {if (parentElement.scrollHeight > parentElement.offsetHeight) {scrollParent = parentElement;targetElement.fastClickScrollParent = parentElement;break;}parentElement = parentElement.parentElement;} while (parentElement);}// 給滾動容器加個標志fastClickLastScrollTop,值為其當前垂直滾動偏移if (scrollParent) {scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;}};/*** @param {EventTarget} targetElement* @returns {Element|EventTarget}*/FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {// 一些較老的瀏覽器,target 可能會是一個文本節點,得返回其DOM節點if (eventTarget.nodeType === Node.TEXT_NODE) {return eventTarget.parentNode;}return eventTarget;};FastClick.prototype.onTouchStart = function(event) {var targetElement, touch, selection;// 多指觸控的手勢則忽略if (event.targetTouches.length > 1) {return true;}targetElement = this.getTargetElementFromEventTarget(event.target); //一些較老的瀏覽器,target 可能會是一個文本節點,得返回其DOM節點touch = event.targetTouches[0];if (deviceIsIOS) { //IOS處理// 若用戶已經選中了一些內容(比如選中了一段文本打算復制),則忽略selection = window.getSelection();if (selection.rangeCount && !selection.isCollapsed) {return true;}if (!deviceIsIOS4) { //是否IOS4//怪異特性處理——若click事件回調打開了一個alert/confirm,用戶下一次tap頁面的其它地方時,新的touchstart和touchend//事件會擁有同一個touch.identifier(新的 touch event 會跟上一次觸發alert點擊的 touch event 一樣),//為避免將新的event當作之前的event導致問題,這里需要禁用默認事件//另外chrome的開發工具啟用'Emulate touch events'后,iOS UA下的 identifier 會變成0,所以要做容錯避免調試過程也被禁用事件了if (touch.identifier && touch.identifier === this.lastTouchIdentifier) {event.preventDefault();return false;}this.lastTouchIdentifier = touch.identifier;// 如果target是一個滾動容器里的一個子元素(使用了 -webkit-overflow-scrolling: touch) ,而且滿足:// 1) 用戶非??焖俚貪L動外層滾動容器// 2) 用戶通過tap停止住了這個快速滾動// 這時候最后的'touchend'的event.target會變成用戶最終手指下的那個元素// 所以當快速滾動開始的時候,需要做檢查target是否滾動容器的子元素,如果是,做個標記// 在touchend時檢查這個標記的值(滾動容器的scrolltop)是否改變了,如果是則說明頁面在滾動中,需要取消fastclick處理this.updateScrollParent(targetElement);}}this.trackingClick = true; //做個標志表示開始追蹤click事件了this.trackingClickStart = event.timeStamp; //標記下touch事件開始的時間戳this.targetElement = targetElement;//標記touch起始點的頁面偏移值this.touchStartX = touch.pageX;this.touchStartY = touch.pageY;// this.lastClickTime 是在 touchend 里標記的事件時間戳// this.tapDelay 為常量 200 (ms)// 此舉用來避免 phantom 的雙擊(200ms內快速點了兩次)觸發 click// 反正200ms內的第二次點擊會禁止觸發點擊的默認事件if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {event.preventDefault();}return true;};//判斷是否移動了//this.touchBoundary是常量,值為10//如果touch已經移動了10個偏移量單位,則應當作為移動事件處理而非click事件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;};FastClick.prototype.onTouchMove = function(event) {//不是需要被追蹤click的事件則忽略if (!this.trackingClick) {return true;}// 如果target突然改變了,或者用戶其實是在移動手勢而非想要click// 則應該清掉this.trackingClick和this.targetElement,告訴后面的事件你們也不用處理了if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {this.trackingClick = false;this.targetElement = null;}return true;};//找到label標簽所映射的組件,方便讓用戶點label的時候直接激活該組件FastClick.prototype.findControl = function(labelElement) {// 有緩存則直接讀緩存著的if (labelElement.control !== undefined) {return labelElement.control;}// 獲取指向的組件if (labelElement.htmlFor) {return document.getElementById(labelElement.htmlFor);}// 沒有for屬性則激活頁面第一個組件(labellable 元素)return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');};FastClick.prototype.onTouchEnd = function(event) {var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;if (!this.trackingClick) {return true;}// 避免 phantom 的雙擊(200ms內快速點了兩次)觸發 click// 我們在 ontouchstart 里已經做過一次判斷了(僅僅禁用默認事件),這里再做一次判斷if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {this.cancelNextClick = true; //該屬性會在 onMouse 事件中被判斷,為true則徹底禁用事件和冒泡return true;}//this.tapTimeout是常量,值為700//識別是否為長按事件,如果是(大于700ms)則忽略if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {return true;}// 得重置為false,避免input事件被意外取消// 例子見 https://github.com/ftlabs/fastclick/issues/156this.cancelNextClick = false;this.lastClickTime = event.timeStamp; //標記touchend時間,方便下一次的touchstart做雙擊校驗
trackingClickStart = this.trackingClickStart;//重置 this.trackingClick 和 this.trackingClickStartthis.trackingClick = false;this.trackingClickStart = 0;// iOS 6.0-7.*版本下有個問題 —— 如果layer處于transition或scroll過程,event所提供的target是不正確的// 所以咱們得重找 targetElement(這里通過 document.elementFromPoint 接口來尋找)if (deviceIsIOSWithBadTarget) { //iOS 6.0-7.*版本touch = event.changedTouches[0]; //手指離開前的觸點// 有些情況下 elementFromPoint 里的參數是預期外/不可用的, 所以還得避免 targetElement 為 nulltargetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;// target可能不正確需要重找,但fastClickScrollParent是不會變的targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;}targetTagName = targetElement.tagName.toLowerCase();if (targetTagName === 'label') { //是label則激活其指向的組件forElement = this.findControl(targetElement);if (forElement) {this.focus(targetElement);//安卓直接返回(無需合成click事件觸發,因為點擊和激活元素不同,不存在點透)if (deviceIsAndroid) {return false;}targetElement = forElement;}} else if (this.needsFocus(targetElement)) { //非label則識別是否需要focus的元素//手勢停留在組件元素時長超過100ms,則置空this.targetElement并返回//(而不是通過調用this.focus來觸發其聚焦事件,走的原生的click/focus事件觸發流程)//這也是為何文章開頭提到的問題中,稍微久按一點(超過100ms)textarea是可以把光標定位在正確的地方的原因//另外iOS下有個意料之外的bug——如果被點擊的元素所在文檔是在iframe中的,手動調用其focus的話,//會發現你往其中輸入的text是看不到的(即使value做了更新),so這里也直接返回if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {this.targetElement = null;return false;}this.focus(targetElement);this.sendClick(targetElement, event); //立即觸發其click事件,而無須等待300ms//iOS4下的 select 元素不能禁用默認事件(要確保它能被穿透),否則不會打開select目錄//有時候 iOS6/7 下(VoiceOver開啟的情況下)也會如此if (!deviceIsIOS || targetTagName !== 'select') {this.targetElement = null;event.preventDefault();}return false;}if (deviceIsIOS && !deviceIsIOS4) {// 滾動容器的垂直滾動偏移改變了,說明是容器在做滾動而非點擊,則忽略scrollParent = targetElement.fastClickScrollParent;if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {return true;}}// 查看元素是否無需處理的白名單內(比如加了名為“needsclick”的class)// 不是白名單的則照舊預防穿透處理,立即觸發合成的click事件if (!this.needsClick(targetElement)) {event.preventDefault();this.sendClick(targetElement, event);}return false;};FastClick.prototype.onTouchCancel = function() {this.trackingClick = false;this.targetElement = null;};//用于決定是否允許穿透事件(觸發layer的click默認事件)FastClick.prototype.onMouse = function(event) {// touch事件一直沒觸發if (!this.targetElement) {return true;}if (event.forwardedTouchEvent) { //觸發的click事件是合成的return true;}// 編程派生的事件所對應元素事件可以被允許// 確保其沒執行過 preventDefault 方法(event.cancelable 不為 true)即可if (!event.cancelable) {return true;}// 需要做預防穿透處理的元素,或者做了快速(200ms)雙擊的情況if (!this.needsClick(this.targetElement) || this.cancelNextClick) {//停止當前默認事件和冒泡if (event.stopImmediatePropagation) {event.stopImmediatePropagation();} else {// 不支持 stopImmediatePropagation 的設備(比如Android 2)做標記,// 確保該事件回調不會執行(見126行)event.propagationStopped = true;}// 取消事件和冒泡
event.stopPropagation();event.preventDefault();return false;}//允許穿透return true;};//click事件常規都是touch事件衍生來的,也排在touch后面觸發。//對于那些我們在touch事件過程沒有禁用掉默認事件的event來說,我們還需要在click的捕獲階段進一步//做判斷決定是否要禁掉點擊事件(防穿透)FastClick.prototype.onClick = function(event) {var permitted;// 如果還有 trackingClick 存在,可能是某些UI事件阻塞了touchEnd 的執行if (this.trackingClick) {this.targetElement = null;this.trackingClick = false;return true;}// 依舊是對 iOS 怪異行為的處理 —— 如果用戶點擊了iOS模擬器里某個表單中的一個submit元素// 或者點擊了彈出來的鍵盤里的“Go”按鈕,會觸發一個“偽”click事件(target是一個submit-type的input元素)if (event.target.type === 'submit' && event.detail === 0) {return true;}permitted = this.onMouse(event);if (!permitted) { //如果點擊是被允許的,將this.targetElement置空可以確保onMouse事件里不會阻止默認事件this.targetElement = null;}//沒有多大意義return permitted;};//銷毀Fastclick所注冊的監聽事件。是給外部實例去調用的FastClick.prototype.destroy = function() {var layer = this.layer;if (deviceIsAndroid) {layer.removeEventListener('mouseover', this.onMouse, true);layer.removeEventListener('mousedown', this.onMouse, true);layer.removeEventListener('mouseup', this.onMouse, true);}layer.removeEventListener('click', this.onClick, true);layer.removeEventListener('touchstart', this.onTouchStart, false);layer.removeEventListener('touchmove', this.onTouchMove, false);layer.removeEventListener('touchend', this.onTouchEnd, false);layer.removeEventListener('touchcancel', this.onTouchCancel, false);};//是否沒必要使用到 Fastclick 的檢測FastClick.notNeeded = function(layer) {var metaViewport;var chromeVersion;var blackberryVersion;var firefoxVersion;// 不支持觸摸的設備if (typeof window.ontouchstart === 'undefined') {return true;}// 獲取Chrome版本號,若非Chrome則返回0chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];if (chromeVersion) {if (deviceIsAndroid) { //安卓metaViewport = document.querySelector('meta[name=viewport]');if (metaViewport) {// 安卓下,帶有 user-scalable="no" 的 meta 標簽的 chrome 是會自動禁用 300ms 延遲的,所以無需 Fastclickif (metaViewport.content.indexOf('user-scalable=no') !== -1) {return true;}// 安卓Chrome 32 及以上版本,若帶有 width=device-width 的 meta 標簽也是無需 FastClick 的if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) {return true;}}// 其它的就肯定是桌面級的 Chrome 了,更不需要 FastClick 啦} else {return true;}}if (deviceIsBlackBerry10) { //黑莓,和上面安卓同理,就不寫注釋了blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/);if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) {metaViewport = document.querySelector('meta[name=viewport]');if (metaViewport) {if (metaViewport.content.indexOf('user-scalable=no') !== -1) {return true;}if (document.documentElement.scrollWidth <= window.outerWidth) {return true;}}}}// 帶有 -ms-touch-action: none / manipulation 特性的 IE10 會禁用雙擊放大,也沒有 300ms 時延if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') {return true;}// Firefox檢測,同上firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];if (firefoxVersion >= 27) {metaViewport = document.querySelector('meta[name=viewport]');if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) {return true;}}// IE11 推薦使用沒有“-ms-”前綴的 touch-action 樣式特性名if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {return true;}return false;};FastClick.attach = function(layer, options) {return new FastClick(layer, options);};if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {// AMD. Register as an anonymous module.define(function() {return FastClick;});} else if (typeof module !== 'undefined' && module.exports) {module.exports = FastClick.attach;module.exports.FastClick = FastClick;} else {window.FastClick = FastClick;}
}());
?來自:http://www.cnblogs.com/vajoy/p/5522114.html
?
下面的是自己改寫的輕量的 fastclick
; (function() {var isMobile = !!navigator.userAgent.match(/mobile/i)&&!!("ontouchstart" in window),isWinPhone = !!navigator.userAgent.match(/Windows Phone/i),isApple = !!navigator.userAgent.match(/(iPad|iPod|iPhone)/i) && !isWinPhone,isAndroid = !!navigator.userAgent.match(/android/i) && !isWinPhone,supportPointer = !!window.navigator.pointerEnabled || !!window.navigator.msPointerEnabled;if (supportPointer) { // 支持pointer的設備可用樣式來取消click事件的300毫秒延遲document.body.style.msTouchAction = "none";document.body.style.touchAction = "none"; } else if (isMobile) {var touchX, touchY,labelControl = null, // label綁定元素focusElement = null, // 當前焦點元素startTime=0,endTime=+new Date,cancelNextClick = false,cancelClick; // 是否取消點擊行為function sentClick(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 trackedclickEvent = document.createEvent('MouseEvents');clickEvent.initMouseEvent(determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);clickEvent.forwardedTouchEvent = true; // fastclick的內部變量,用來識別click事件是原生還是合成的targetElement.dispatchEvent(clickEvent); }//安卓設備下 Select 無法通過合成的 click 事件被展開,得改為 mousedownfunction determineEventType(targetElement) {if (isAndroid && targetElement.tagName.toLowerCase() === 'select') {return 'mousedown';}return 'click';}; document.addEventListener("touchstart", function(e) {var touch = e.changedTouches[0];touchX = touch.pageX;touchY = touch.pageY;startTime = +new Date;cancelClick = false;// if ((endTime-startTime) < 200) { // 此舉用來避免 phantom 的雙擊(200ms內快速點了兩次)觸發 click// e.preventDefault();// cancelNextClick = true;// }}, false);document.addEventListener("touchmove", function(e) {var touch = e.changedTouches[0];if ( e.touches.length > 1 || e.scale && e.scale !== 1) return// 水平或垂直方向移動超過15px測判定為取消(根據chrome瀏覽器默認的判斷取消點擊的移動量)if (Math.abs(touch.pageX - touchX) > 29|| Math.abs(touch.pageY - touchY) > 29) {cancelClick = true;}}, false); document.addEventListener("touchend", function(e) {var touch = e.changedTouches[0];endTime= +new Date;var duration=parseInt(endTime - startTime);var isValidSlide =Number(duration)< 250&&Math.abs(touch.pageX - touchX) < 30 || Number(duration)< 250&&Math.abs(touch.touchY - touchY) < 30||Number(duration)> 700&&Math.abs(touch.pageX - touchX) < 30 || Number(duration)> 700&&Math.abs(touch.touchY - touchY) < 30; //長按事件 或者tap 事件if (!isValidSlide||cancelClick) {return;}var is_form = isForm(e.target),is_text = isText(e.target),is_select = isSelect(e.target),is_checkbox = isCheckbox(e.target),is_disabled = isDisabled(e.target);if (is_form) { // 如果是表單元素,則讓其獲取焦點focusElement = e.target;focusElement.focus();if (isAndroid && is_select) { // 在Android設備上,如果是select,則單獨轉發一個mousedown事件,用于激活選擇列表sentClick( e.target,e)}} else { // 如果不是表單元素,則判斷事件源節點是否處于label標簽中if (focusElement) {focusElement.blur();focusElement = null;}labelControl = null;var node = e.target;while (node) {if (node.tagName == "BODY") break;else if (node.tagName == "LABEL") {// 當非表單元素觸發的click事件冒泡到label層時,label都會使自己綁定的表單元素單獨再觸發一次click事件// 這里需要獲取label的綁定表單元素,以便之后在click事件監聽中不阻止該元素的事件冒泡if (node.control) labelControl = node.control;break;}node = node.parentNode;}}if ((!is_disabled && !is_text)// 如果該元素不是禁用狀態,且不是文本輸入框,才派發click事件// 因為不是禁用狀態的文本輸入框再之前已經被設置為焦點,同時激活了鍵盤,使文本框自身產生了位移// 此時再派發click事件將會使文本輸入框失去焦點|| (is_disabled && !is_checkbox)) {// 或者如果是禁用狀態,但不是checkbox或者radio,才派發click事件// 因為禁用狀態下的checkbox和radio會被這里派發的click事件激活sentClick( e.target,e)}if (isApple) { // IOS設備消除touchend后300ms觸發的click事件// 如果不是可用文本域才阻止瀏覽器的默認行為// 因為文本彈出編輯菜單和指定光標到某一文本段落的動作需要瀏覽器默認行為的支持if (is_disabled || !is_text) e.preventDefault();} else if (isAndroid) { // Android設備消除touchend后150ms觸發的mousedown事件// 如果不是文本域才阻止瀏覽器的默認行為// 即使文本域不可用,也不可以阻止瀏覽器的默認行為,因為這樣會使不可用的文本域呼出虛擬鍵盤if (!is_text) e.preventDefault();}}, false);document.addEventListener("click", function(e) {if (e.target == labelControl) { // 如果是label綁定的表單元素,則設置為焦點元素focusElement = e.target;focusElement.focus();labelControl = null;} else if (!e.forwardedTouchEvent) { // 其余所有非touch觸發的click事件全部阻止 不是合成的事件focusElement && focusElement.focus(); // 取消焦點的行為無法被阻斷,因此需要再為焦點元素設置一次焦點e.preventDefault();e.stopImmediatePropagation();return false;}}, true);if (isAndroid) {// Android設備上,文本域的touchend事件中沒有阻止瀏覽器的默認行為// 因此在這里來阻止之后的 click 事件document.addEventListener("mousedown", function(e) {// 在mousedown事件中消除touchend后300ms觸發的click事件,防止點透e.stopImmediatePropagation();}, true); // 事件必須注冊在捕獲階段}function isForm(target) {switch (target.tagName) {case 'BUTTON':case 'TEXTAREA':case 'SELECT':case 'INPUT':return true;default:return false;}}function isSelect(target) {return target.tagName == 'SELECT';}function isText(target) {switch (target.tagName) {case 'TEXTAREA':return true;case 'INPUT':switch (target.type) {case 'text':case 'search':case 'password':case 'tel':case 'email':case 'url':return true;}return false;default:return false;}}function isCheckbox(target) {switch (target.tagName) {case 'INPUT':switch (target.type) {case 'checkbox':case 'radio':return true;}return false;default:return false;}}function isDisabled(target) {return target.hasAttribute("disabled") || target.hasAttribute("readonly");} }})();fastclick模擬的click事件是在touchEnd獲取的真實元素上觸發的,而不是通過坐標計算出來的元素?!?/p>
fastclick流程:touchstart、獲取event.target、touchend、阻止瀏覽器默認事件click(阻止了300ms后的click事件執行)、程序創建點擊事件并在event.target觸發(此觸發事件不延遲)
在沒有使用fastclick的情況下,由于touchend結束后不確定用戶手指是要再點擊構成雙擊還是就此結束構成單擊,所以等300ms(touchstart->touchend->300ms后click).
fastclick:
touchstart獲取event.target,
在touchend 阻止默認事件(event.preventDefault),即阻止了300ms后的click事件執行,
然后再程序創建點擊事件并在event.target觸發(dispatchEvent)【程序觸發點擊事件由于不存在不確定下一步行為問題,所以立即執行click事件,沒有延遲】;
function sentClick(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 trackedclickEvent = document.createEvent('MouseEvents');clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);targetElement.dispatchEvent(clickEvent); }var clickEvent;var stopClick;var clickTime;document.addEventListener('touchstart', function (e) {clickEvent = e.target;clickTime = e.timeStamp}, false);document.addEventListener('touchend', function (e) {// if ( (e.timeStamp - clickTime < 300)) {sentClick( e.target,e);e.preventDefault();// }}, false);核心原理如下? 代碼
?
<!DOCTYPE HTML> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"><title>點擊穿透問題</title><style type="text/css">body{margin: 0;}.container{width: 100%;overflow: hidden;position: relative;}.layer-title{width: 100%;margin: 50px 0;text-align: center;}.layer-action{position: absolute;bottom: 20px;width: 100%;text-align: center;}.btn{background-color: #08c;border: 0;color: #fff;height: 30px;line-height: 30px;width: 100px;}#underLayer{background-color: #eee;width: 90%;height: 500px;line-height: 500px;margin: 30px auto 1000px;text-align: center;}#popupLayer{/*display: none;*/background-color: #fff;width: 80%;height: 200px;position: fixed;top: 50%;left: 50%;margin-left: -40%;margin-top: -100px;z-index: 1;}#bgMask{position: fixed;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(0,0,0,0.6);}</style> </head> <body><div class="container"><div id="underLayer">底層元素</div><div id="popupLayer"><div class="layer-title">彈出層</div><div class="layer-action"><span class="btn" id="closePopup">關閉</span></div></div></div><div id="bgMask"></div><script type="text/javascript" src="//cdn.bootcss.com/zepto/1.0rc1/zepto.min.js"></script><script type="text/javascript">Zepto(function($){// ; (function() { // //var isMobile = !!navigator.userAgent.match(/mobile/i)&&!!("ontouchstart" in window), // isWinPhone = !!navigator.userAgent.match(/Windows Phone/i), // isApple = !!navigator.userAgent.match(/(iPad|iPod|iPhone)/i) && !isWinPhone, // isAndroid = !!navigator.userAgent.match(/android/i) && !isWinPhone, // supportPointer = !!window.navigator.pointerEnabled || !!window.navigator.msPointerEnabled; // //if (supportPointer) { // 支持pointer的設備可用樣式來取消click事件的300毫秒延遲 // document.body.style.msTouchAction = "none"; // document.body.style.touchAction = "none"; //} else if (isMobile) { // var touchX, touchY, // labelControl = null, // label綁定元素 // focusElement = null, // 當前焦點元素 // startTime=0, // endTime=+new Date, // cancelNextClick = false, // cancelClick; // 是否取消點擊行為 // // // function sentClick(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(determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); // clickEvent.forwardedTouchEvent = true; // fastclick的內部變量,用來識別click事件是原生還是合成的 // targetElement.dispatchEvent(clickEvent); // // } // // //安卓設備下 Select 無法通過合成的 click 事件被展開,得改為 mousedown // function determineEventType(targetElement) { // if (isAndroid && targetElement.tagName.toLowerCase() === 'select') { // return 'mousedown'; // } // return 'click'; // }; // // document.addEventListener("touchstart", function(e) { // var touch = e.changedTouches[0]; // touchX = touch.pageX; // touchY = touch.pageY; // startTime = +new Date; // cancelClick = false; // // // if ((endTime-startTime) < 200) { // 此舉用來避免 phantom 的雙擊(200ms內快速點了兩次)觸發 click // // e.preventDefault(); // // cancelNextClick = true; // // } // // // }, false); // // document.addEventListener("touchmove", function(e) { // var touch = e.changedTouches[0]; // if ( e.touches.length > 1 || e.scale && e.scale !== 1) return // // 水平或垂直方向移動超過15px測判定為取消(根據chrome瀏覽器默認的判斷取消點擊的移動量) // if (Math.abs(touch.pageX - touchX) > 29|| Math.abs(touch.pageY - touchY) > 29) { // cancelClick = true; // } // }, false); // // // document.addEventListener("touchend", function(e) { // var touch = e.changedTouches[0]; // endTime= +new Date; // var duration=parseInt(endTime - startTime); // var isValidSlide =Number(duration)< 250&&Math.abs(touch.pageX - touchX) < 30 || Number(duration)< 250&&Math.abs(touch.touchY - touchY) < 30||Number(duration)> 700&&Math.abs(touch.pageX - touchX) < 30 || Number(duration)> 700&&Math.abs(touch.touchY - touchY) < 30; //長按事件 或者tap 事件 // // // // if (!isValidSlide||cancelClick) { // return; // } // // // // var is_form = isForm(e.target), // is_text = isText(e.target), // is_select = isSelect(e.target), // is_checkbox = isCheckbox(e.target), // is_disabled = isDisabled(e.target); // // if (is_form) { // 如果是表單元素,則讓其獲取焦點 // focusElement = e.target; // focusElement.focus(); // if (isAndroid && is_select) { // 在Android設備上,如果是select,則單獨轉發一個mousedown事件,用于激活選擇列表 // sentClick( e.target,e) // } // } else { // 如果不是表單元素,則判斷事件源節點是否處于label標簽中 // if (focusElement) { // focusElement.blur(); // focusElement = null; // } // labelControl = null; // var node = e.target; // while (node) { // if (node.tagName == "BODY") break; // else if (node.tagName == "LABEL") { // // 當非表單元素觸發的click事件冒泡到label層時,label都會使自己綁定的表單元素單獨再觸發一次click事件 // // 這里需要獲取label的綁定表單元素,以便之后在click事件監聽中不阻止該元素的事件冒泡 // if (node.control) labelControl = node.control; // break; // } // node = node.parentNode; // } // } // // if ((!is_disabled && !is_text) // // 如果該元素不是禁用狀態,且不是文本輸入框,才派發click事件 // // 因為不是禁用狀態的文本輸入框再之前已經被設置為焦點,同時激活了鍵盤,使文本框自身產生了位移 // // 此時再派發click事件將會使文本輸入框失去焦點 // || (is_disabled && !is_checkbox)) { // // 或者如果是禁用狀態,但不是checkbox或者radio,才派發click事件 // // 因為禁用狀態下的checkbox和radio會被這里派發的click事件激活 // console.log(e.target.tagName); // sentClick( e.target,e) // } // // if (isApple) { // IOS設備消除touchend后300ms觸發的click事件 // // 如果不是可用文本域才阻止瀏覽器的默認行為 // // 因為文本彈出編輯菜單和指定光標到某一文本段落的動作需要瀏覽器默認行為的支持 // if (is_disabled || !is_text) e.preventDefault(); // } else if (isAndroid) { // Android設備消除touchend后150ms觸發的mousedown事件 // // 如果不是文本域才阻止瀏覽器的默認行為 // // 即使文本域不可用,也不可以阻止瀏覽器的默認行為,因為這樣會使不可用的文本域呼出虛擬鍵盤 // if (!is_text) e.preventDefault(); // } // }, false); // document.addEventListener("click", function(e) {if (e.target == labelControl) { // 如果是label綁定的表單元素,則設置為焦點元素focusElement = e.target;focusElement.focus();labelControl = null;} else if (!e.forwardedTouchEvent) { // 其余所有非touch觸發的click事件全部阻止 不是合成的事件focusElement && focusElement.focus(); // 取消焦點的行為無法被阻斷,因此需要再為焦點元素設置一次焦點e.preventDefault();e.stopImmediatePropagation();return false;}}, true); // // if (isAndroid) { // // Android設備上,文本域的touchend事件中沒有阻止瀏覽器的默認行為 // // 因此在這里來阻止之后的 click 事件 // document.addEventListener("mousedown", function(e) { // // 在mousedown事件中消除touchend后300ms觸發的click事件,防止點透 // e.stopImmediatePropagation(); // }, true); // 事件必須注冊在捕獲階段 // } // // function isForm(target) { // switch (target.tagName) { // case 'BUTTON': // case 'TEXTAREA': // case 'SELECT': // case 'INPUT': // return true; // default: // return false; // } // } // function isSelect(target) { // return target.tagName == 'SELECT'; // } // function isText(target) { // switch (target.tagName) { // case 'TEXTAREA': // return true; // case 'INPUT': // switch (target.type) { // case 'text': // case 'search': // case 'password': // case 'tel': // case 'email': // case 'url': // return true; // } // return false; // default: // return false; // } // } // function isCheckbox(target) { // switch (target.tagName) { // case 'INPUT': // switch (target.type) { // case 'checkbox': // case 'radio': // return true; // } // return false; // default: // return false; // } // } // function isDisabled(target) { // return target.hasAttribute("disabled") || target.hasAttribute("readonly"); // } //} // //})();function sentClick(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 trackedclickEvent = document.createEvent('MouseEvents');clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);targetElement.dispatchEvent(clickEvent); }var clickEvent;var stopClick;var clickTime;document.addEventListener('touchstart', function (e) {clickEvent = e.target;clickTime = e.timeStamp}, false);document.addEventListener('touchend', function (e) {// if ( (e.timeStamp - clickTime < 300)) {sentClick( e.target,e);e.preventDefault();// }}, false);//http://192.168.0.105:8020/try/tap2.html// 點擊穿透var $close = $('#closePopup');var $popup = $('#popupLayer');var $under = $('#underLayer');var $mask = $('#bgMask');$close.on('tap', function(e){$popup.hide();$mask.hide();});$under.on('click', function(){alert('underLayer clicked');});});</script> </body>
轉載于:https://www.cnblogs.com/surfaces/p/6029576.html
總結
以上是生活随笔為你收集整理的fastclick 源码阅读备份的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java项目使用阿里云平台发送短信说明
- 下一篇: 《乐高EV3机器人搭建与编程》一2.8