通過(guò)fastclick源碼分析徹底解決tap“點(diǎn)透”
近期使用tap事件為老夫帶來(lái)了這樣那樣的問(wèn)題,其中一個(gè)問(wèn)題是解決了點(diǎn)透還需要將原來(lái)一個(gè)個(gè)click變?yōu)閠ap,這樣的話(huà)我們就拋棄了ie用戶(hù)
當(dāng)然可以做兼容,但是沒(méi)人想動(dòng)老代碼的,于是今天拿出了fastclick這個(gè)東西,
這是最近第四次發(fā)文說(shuō)tap的點(diǎn)透事件,我們一直對(duì)解決“點(diǎn)透”的蒙版耿耿于懷,于是今天老大提出了一個(gè)庫(kù)fastclick,最后證明解決了我們的問(wèn)題
而且click不必替換為tap了,于是我們老大就語(yǔ)重心長(zhǎng)的對(duì)我說(shuō)了一句,你們就誤我吧,我郵件都發(fā)出去了......
于是我下午就在看fastclick這個(gè)庫(kù),看看是不是能解決我們的問(wèn)題,于是我們開(kāi)始吧
讀fastclick源碼
尼瑪使用太簡(jiǎn)單了,直接一句:
FastClick.attach(document.body);
于是所有的click響應(yīng)速度直接提升,剛剛的!什么input獲取焦點(diǎn)的問(wèn)題也解決了?。?!尼瑪如果真的可以的話(huà),原來(lái)改頁(yè)面的同事肯定會(huì)啃了我
一步步來(lái),我們跟進(jìn)去,入口就是attach方法:
FastClick.attach = function(layer) {
'use strict';
return new FastClick(layer);
};
這個(gè)兄弟不過(guò)實(shí)例化了下代碼,所以我們還要看我們的構(gòu)造函數(shù):
function FastClick(layer) {
'use strict';
var oldOnClick, self = this;
this.trackingClick = false;
this.trackingClickStart = 0;
this.targetElement = null;
this.touchStartX = 0;
this.touchStartY = 0;
this.lastTouchIdentifier = 0;
this.touchBoundary = 10;
this.layer = layer;
if (!layer || !layer.nodeType) {
throw new TypeError('Layer must be a document node');
}
this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); };
this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); };
this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); };
this.onTouchMove = function() { return FastClick.prototype.onTouchMove.apply(self, arguments); };
this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); };
this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); };
if (FastClick.notNeeded(layer)) {
return;
}
if (this.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);
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') {
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {
callback(event);
}
}), capture);
} else {
adv.call(layer, type, callback, capture);
}
};
}
if (typeof layer.onclick === 'function') {
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
看看這段代碼,上面很多屬性干了什么事情我也不知道......于是忽略了
if (!layer || !layer.nodeType) {
throw new TypeError('Layer must be a document node');
}
其中這里要注意,我們必須傳入一個(gè)節(jié)點(diǎn)給構(gòu)造函數(shù),否則會(huì)出問(wèn)題
然后這個(gè)家伙將一些基本的鼠標(biāo)事件注冊(cè)在自己的屬性方法上了,具體是干神馬的我們后面再說(shuō)
在后面點(diǎn)有個(gè)notNeeded方法:
FastClick.notNeeded = function(layer) {
'use strict';
var metaViewport;
if (typeof window.ontouchstart === 'undefined') {
return true;
}
if ((/Chrome\/[0-9]+/).test(navigator.userAgent)) {
if (FastClick.prototype.deviceIsAndroid) {
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport && metaViewport.content.indexOf('user-scalable=no') !== -1) {
return true;
}
} else {
return true;
}
}
if (layer.style.msTouchAction === 'none') {
return true;
}
return false;
};
這個(gè)方法用于判斷是否需要用到fastclick,注釋的意思不太明白,我們看看代碼吧
首先一句:
if (typeof window.ontouchstart === 'undefined') {
return true;
}
如果不支持touchstart事件的話(huà),返回true
PS:現(xiàn)在的只管感受就是fastclick應(yīng)該也是以touch事件模擬的,但是其沒(méi)有點(diǎn)透問(wèn)題
后面還判斷了android的一些問(wèn)題,我這里就不關(guān)注了,意思應(yīng)該就是支持touch才能支持吧,于是回到主干代碼
主干代碼中,我們看到,如果瀏覽器不支持touch事件或者其它問(wèn)題就直接跳出了
然后里面有個(gè)deviceIsAndroid的屬性,我們跟去看看(其實(shí)不用看也知道是判斷是否是android設(shè)備)
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
綁定事件
好了,這家伙開(kāi)始綁定注冊(cè)事件了,至此還未看出異樣
if (this.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);
具體的事件函數(shù)在前面被重寫(xiě)了,我們暫時(shí)不管他,繼續(xù)往后面看先(話(huà)說(shuō),這家伙綁定的事件夠多的)
stopImmediatePropagation
完了多了一個(gè)屬性:
阻止當(dāng)前事件的冒泡行為并且阻止當(dāng)前事件所在元素上的所有相同類(lèi)型事件的事件處理函數(shù)的繼續(xù)執(zhí)行.
如果某個(gè)元素有多個(gè)相同類(lèi)型事件的事件監(jiān)聽(tīng)函數(shù),則當(dāng)該類(lèi)型的事件觸發(fā)時(shí),多個(gè)事件監(jiān)聽(tīng)函數(shù)將按照順序依次執(zhí)行.如果某個(gè)監(jiān)聽(tīng)函數(shù)執(zhí)行了 event.stopImmediatePropagation()方法,則除了該事件的冒泡行為被阻止之外(event.stopPropagation方法的作用),該元素綁定的其余相同類(lèi)型事件的監(jiān)聽(tīng)函數(shù)的執(zhí)行也將被阻止.
<html>
<head>
<style>
p { height: 30px; width: 150px; background-color: #ccf; }
div {height: 30px; width: 150px; background-color: #cfc; }
</style>
</head>
<body>
<div>
<p>paragraph</p>
</div>
<script>
document.querySelector("p").addEventListener("click", function(event)
{
alert("我是p元素上被綁定的第一個(gè)監(jiān)聽(tīng)函數(shù)");
}, false);
document.querySelector("p").addEventListener("click", function(event)
{
alert("我是p元素上被綁定的第二個(gè)監(jiān)聽(tīng)函數(shù)");
event.stopImmediatePropagation();
//執(zhí)行stopImmediatePropagation方法,阻止click事件冒泡,并且阻止p元素上綁定的其他click事件的事件監(jiān)聽(tīng)函數(shù)的執(zhí)行.
}, false);
document.querySelector("p").addEventListener("click", function(event)
{
alert("我是p元素上被綁定的第三個(gè)監(jiān)聽(tīng)函數(shù)");
//該監(jiān)聽(tīng)函數(shù)排在上個(gè)函數(shù)后面,該函數(shù)不會(huì)被執(zhí)行.
}, false);
document.querySelector("div").addEventListener("click", function(event)
{
alert("我是div元素,我是p元素的上層元素");
//p元素的click事件沒(méi)有向上冒泡,該函數(shù)不會(huì)被執(zhí)行.
}, false);
</script>
</body>
</html>
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') {
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {
callback(event);
}
}), capture);
} else {
adv.call(layer, type, callback, capture);
}
};
}
然后這家伙重新定義了下注冊(cè)與注銷(xiāo)事件的方法,
我們先看注冊(cè)事件,其中用到了Node的addEventListener,這個(gè)Node是個(gè)什么呢?
由此觀之,Node是一個(gè)系統(tǒng)屬性,代表我們的節(jié)點(diǎn)吧,所以這里重寫(xiě)了注銷(xiāo)的事件
這里,我們發(fā)現(xiàn),其實(shí)他只對(duì)click進(jìn)行了特殊處理
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {
callback(event);
}
}), capture);
其中有個(gè)hijacked劫持是干神馬的就暫時(shí)不知道了,估計(jì)是在中間是否改寫(xiě)的意思吧
然后這里重寫(xiě)寫(xiě)了下,hijacked估計(jì)是一個(gè)方法,就是為了阻止在一個(gè)dom上注冊(cè)多次事件多次執(zhí)行的情況而存在的吧
注銷(xiāo)和注冊(cè)差不多我們就不管了,到此我們其實(shí)重寫(xiě)了我們傳入dom的注冊(cè)注銷(xiāo)事件了,好像很厲害的樣子,意思以后這個(gè)dom調(diào)用click事件用的是我們的,當(dāng)然這只是我暫時(shí)的判斷,具體還要往下讀,而且我覺(jué)得現(xiàn)在的判斷不靠譜,于是我們繼續(xù)吧
我們注銷(xiāo)事件時(shí)候可以用addEventListener 或者 dom.onclick=function(){},所以這里有了下面的代碼:
if (typeof layer.onclick === 'function') {
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
此處,他的主干流程居然就完了,意思是他所有的邏輯就在這里了,不論入口還是出口應(yīng)該就是事件注冊(cè)了,于是我們寫(xiě)個(gè)代碼來(lái)看看
測(cè)試入口
<input type="button" value="addevent">
<input type="button" value="addevent1">
$('#addEvent').click(function () {
var dom = $('#addEvent1')[0]
dom.addEventListener('click', function () {
alert('')
var s = '';
})
});
我們來(lái)這個(gè)斷點(diǎn)看看我們點(diǎn)擊后干了什么,我們現(xiàn)在點(diǎn)擊按鈕1會(huì)為按鈕2注冊(cè)事件:
但是很遺憾,我們?cè)陔娔X上不能測(cè)試,所以增加了我們讀代碼的困難,在手機(jī)上測(cè)試后,發(fā)現(xiàn)按鈕2響應(yīng)很快,但是這里有點(diǎn)看不出問(wèn)題
最后alert了一個(gè)!Event.prototype.stopImmediatePropagation發(fā)現(xiàn)手機(jī)和電腦都是false,所以我們上面搞的東西暫時(shí)無(wú)用
FastClick.prototype.onClick = function (event) {
'use strict';
var permitted;
alert('終于尼瑪進(jìn)來(lái)了');
if (this.trackingClick) {
this.targetElement = null;
this.trackingClick = false;
return true;
}
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
permitted = this.onMouse(event);
if (!permitted) {
this.targetElement = null;
}
return permitted;
};
然后我們終于進(jìn)來(lái)了,現(xiàn)在我們需要知道什么是trackingClick 了
/** * Whether a click is currently being tracked. * @type Boolean */ this.trackingClick = false;
我們最初這個(gè)屬性是false,但是到這里就設(shè)置為true了,就直接退出了,說(shuō)明綁定事件終止,算了這個(gè)我們暫時(shí)不關(guān)注,我們干點(diǎn)其它的,
因?yàn)?,我覺(jué)得重點(diǎn)還是應(yīng)該在touch事件上
PS:到這里,我們發(fā)現(xiàn)這個(gè)庫(kù)應(yīng)該不只是將click加快,而是所有的響應(yīng)都加快了
我在各個(gè)事件部分log出來(lái)東西,發(fā)現(xiàn)有click的地方都只執(zhí)行了touchstart與touchend,于是至此,我覺(jué)得我的觀點(diǎn)成立
他使用touch事件模擬量click,于是我們就只跟進(jìn)這一塊就好:
FastClick.prototype.onTouchStart = function (event) {
'use strict';
var targetElement, touch, selection;
log('touchstart');
if (event.targetTouches.length > 1) {
return true;
}
targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0];
if (this.deviceIsIOS) {
selection = window.getSelection();
if (selection.rangeCount && !selection.isCollapsed) {
return true;
}
if (!this.deviceIsIOS4) {
if (touch.identifier === this.lastTouchIdentifier) {
event.preventDefault();
return false;
}
this.lastTouchIdentifier = touch.identifier;
this.updateScrollParent(targetElement);
}
}
this.trackingClick = true;
this.trackingClickStart = event.timeStamp;
this.targetElement = targetElement;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
if ((event.timeStamp - this.lastClickTime) < 200) {
event.preventDefault();
}
return true;
};
其中用到了一個(gè)方法:
FastClick.prototype.getTargetElementFromEventTarget = function (eventTarget) {
'use strict';
if (eventTarget.nodeType === Node.TEXT_NODE) {
return eventTarget.parentNode;
}
return eventTarget;
};
他是獲取我們當(dāng)前touchstart的元素
然后將鼠標(biāo)的信息記錄了下來(lái),他記錄鼠標(biāo)信息主要在后面touchend時(shí)候根據(jù)x、y判斷是否為click
是ios情況下還搞了一些事情,我這里跳過(guò)去了
然后這里記錄了一些事情就跳出去了,沒(méi)有特別的事情,現(xiàn)在我們進(jìn)入我們的出口touchend
FastClick.prototype.onTouchEnd = function (event) {
'use strict';
var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
log('touchend');
if (!this.trackingClick) {
return true;
}
if ((event.timeStamp - this.lastClickTime) < 200) {
this.cancelNextClick = true;
return true;
}
this.lastClickTime = event.timeStamp;
trackingClickStart = this.trackingClickStart;
this.trackingClick = false;
this.trackingClickStart = 0;
if (this.deviceIsIOSWithBadTarget) {
touch = event.changedTouches[0];
targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
}
targetTagName = targetElement.tagName.toLowerCase();
if (targetTagName === 'label') {
forElement = this.findControl(targetElement);
if (forElement) {
this.focus(targetElement);
if (this.deviceIsAndroid) {
return false;
}
targetElement = forElement;
}
} else if (this.needsFocus(targetElement)) {
if ((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top !== window && targetTagName === 'input')) {
this.targetElement = null;
return false;
}
this.focus(targetElement);
if (!this.deviceIsIOS4 || targetTagName !== 'select') {
this.targetElement = null;
event.preventDefault();
}
return false;
}
if (this.deviceIsIOS && !this.deviceIsIOS4) {
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
}
if (!this.needsClick(targetElement)) {
event.preventDefault();
this.sendClick(targetElement, event);
}
return false;
};
這個(gè)家伙洋洋灑灑干了許多事情
這里糾正一個(gè)錯(cuò)誤,他onclick那些東西現(xiàn)在也執(zhí)行了......可能是我屏幕有變化(滑動(dòng))導(dǎo)致
if ((event.timeStamp - this.lastClickTime) < 200) {
this.cancelNextClick = true;
return true;
}
這個(gè)代碼很關(guān)鍵,我們首次點(diǎn)擊會(huì)執(zhí)行下面的邏輯,如果連續(xù)點(diǎn)擊就直接完蛋,下面的邏輯丫的不執(zhí)行了......
這個(gè)不執(zhí)行了,那么這個(gè)勞什子又干了什么事情呢?
事實(shí)上下面就沒(méi)邏輯了,意思是如果確實(shí)點(diǎn)擊過(guò)快,兩次點(diǎn)擊只會(huì)執(zhí)行一次,這個(gè)閥值為200ms,這個(gè)暫時(shí)看來(lái)是沒(méi)有問(wèn)題的
好了,我們繼續(xù)往下走,于是我意識(shí)到又到了一個(gè)關(guān)鍵點(diǎn)
因?yàn)槲覀冇胻ap事件不能使input獲得焦點(diǎn),但是fastclick卻能獲得焦點(diǎn),這里也許是一個(gè)關(guān)鍵,我們來(lái)看看幾個(gè)與獲取焦點(diǎn)有關(guān)的函數(shù)
FastClick.prototype.focus = function (targetElement) {
'use strict';
var length;
if (this.deviceIsIOS && targetElement.setSelectionRange) {
length = targetElement.value.length;
targetElement.setSelectionRange(length, length);
} else {
targetElement.focus();
}
};
setSelectionRange是我們的關(guān)鍵,也許他是這樣獲取焦點(diǎn)的......具體我還要下來(lái)測(cè)試,留待下次處理吧
然后下面如果時(shí)間間隔過(guò)長(zhǎng),代碼就不認(rèn)為操作的是同一dom結(jié)構(gòu)了
最后迎來(lái)了本次的關(guān)鍵:sendClick,無(wú)論是touchend還是onMouse都會(huì)匯聚到這里
FastClick.prototype.sendClick = function (targetElement, event) {
'use strict';
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('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
};
他創(chuàng)建了一個(gè)鼠標(biāo)事件,然后dispatchEvent事件(這個(gè)與fireEvent類(lèi)似)
//document上綁定自定義事件ondataavailable
document.addEventListener('ondataavailable', function (event) {
alert(event.eventType);
}, false);
var obj = document.getElementById("obj");
//obj元素上綁定click事件
obj.addEventListener('click', function (event) {
alert(event.eventType);
}, false);
//調(diào)用document對(duì)象的 createEvent 方法得到一個(gè)event的對(duì)象實(shí)例。
var event = document.createEvent('HTMLEvents');
// initEvent接受3個(gè)參數(shù):
// 事件類(lèi)型,是否冒泡,是否阻止瀏覽器的默認(rèn)行為
event.initEvent("ondataavailable", true, true);
event.eventType = 'message';
//觸發(fā)document上綁定的自定義事件ondataavailable
document.dispatchEvent(event);
var event1 = document.createEvent('HTMLEvents');
event1.initEvent("click", true, true);
event1.eventType = 'message';
//觸發(fā)obj元素上綁定click事件
document.getElementById("test").onclick = function () {
obj.dispatchEvent(event1);
};
至此,我們就知道了,我們?yōu)閐om先綁定了鼠標(biāo)事件,然后touchend時(shí)候觸發(fā)了,而至于為什么本身注冊(cè)的click未觸發(fā)就要回到上面代碼了
解決“點(diǎn)透”(成果)
有了這個(gè)思路,我們來(lái)試試我們抽象出來(lái)的代碼:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<style>
#list { display: block; position: absolute; top: 100px; left: 10px; width: 200px; height: 100px; }
div { display: block; border: 1px solid black; height: 300px; width: 100%; }
#input { width: 80px; height: 200px; display: block; }
</style>
</head>
<body>
<div>
</div>
<div>
<div>
<input type="text" />
</div>
</div>
<script type="text/javascript">
var el = null;
function getEvent(el, e, type) {
e = e.changedTouches[0];
var event = document.createEvent('MouseEvents');
event.initMouseEvent(type, true, true, window, 1, e.screenX, e.screenY, e.clientX, e.clientY, false, false, false, false, 0, null);
event.forwardedTouchEvent = true;
return event;
}
list.addEventListener('touchstart', function (e) {
var firstTouch = e.touches[0]
el = firstTouch.target;
t1 = e.timeStamp;
})
list.addEventListener('touchend', function (e) {
e.preventDefault();
var event = getEvent(el, e, 'click');
el.dispatchEvent(event);
})
var list = document.getElementById('list');
list.addEventListener('click', function (e) {
list.style.display = 'none';
setTimeout(function () {
list.style.display = '';
}, 1000);
})
</script>
</body>
</html>
這樣的話(huà),便不會(huì)點(diǎn)透了,這是因?yàn)閦epto touch事件全部綁定值document,所以 e.preventDefault();無(wú)用
結(jié)果我們這里是直接在dom上,e.preventDefault();
便起了作用不會(huì)觸發(fā)瀏覽器默認(rèn)事件,所以也不存在點(diǎn)透問(wèn)題了,至此點(diǎn)透事件告一段落......
幫助理解的圖
代碼在公司寫(xiě)的,回家后不知道圖上哪里了,各位將就看吧





為什么zepto會(huì)點(diǎn)透/fastclick如何解決點(diǎn)透
我最開(kāi)始就給老大說(shuō)zepto處理tap事件不夠好,搞了很多事情出來(lái)
因?yàn)樗录墙壎ǖ絛ocument上,先touchstart然后touchend,根據(jù)touchstart的event參數(shù)判斷該dom是否注冊(cè)了tap事件,有就觸發(fā)
于是問(wèn)題來(lái)了,zepto的touchend這里有個(gè)event參數(shù),我們event.preventDefault(),這里本來(lái)都是最上層了,這就代碼壓根沒(méi)什么用
但是fastclick處理辦法不可謂不巧妙,這個(gè)庫(kù)直接在touchend的時(shí)候就觸發(fā)了dom上的click事件而替換了本來(lái)的觸發(fā)時(shí)間
意思是原來(lái)要350-400ms執(zhí)行的代碼突然就移到了50-100ms,然后這里雖然使用了touch事件但是touch事件是綁定到了具體dom而不是document上
所以e.preventDefault是有效的,我們可以阻止冒泡,也可以阻止瀏覽器默認(rèn)事件,這個(gè)才是fastclick的精華部分,不可謂不高?。。?!
整個(gè)fastclick代碼讀來(lái)醍醐灌頂,今天收獲很大,在此記錄
后記
上面的說(shuō)法有點(diǎn)問(wèn)題,這修正一下:
首先,我們回到原來(lái)的zepto方案,看看他有什么問(wèn)題:
因?yàn)閖s標(biāo)準(zhǔn)本不支持tap事件,所以zepto tap是touchstart與touchend模擬而出 zepto在初始化時(shí)便給document綁定touch事件,在我們點(diǎn)擊時(shí)根據(jù)event參數(shù)獲得當(dāng)前元素,并會(huì)保存點(diǎn)下和離開(kāi)時(shí)候的鼠標(biāo)位置 根據(jù)當(dāng)前元素鼠標(biāo)移動(dòng)范圍判斷是否為類(lèi)點(diǎn)擊事件,如果是便觸發(fā)已經(jīng)注冊(cè)好的tap事件
然后fastclick處理比較與zepto基本一致,但是又有所不同
fastclick是將事件綁定到你傳的元素(一般是document.body)
② 在touchstart和touchend后(會(huì)手動(dòng)獲取當(dāng)前點(diǎn)擊el),如果是類(lèi)click事件便手動(dòng)觸發(fā)了dom元素的click事件
所以click事件在touchend便被觸發(fā),整個(gè)響應(yīng)速度就起來(lái)了,觸發(fā)實(shí)際與zepto tap一樣
好了,為什么基本相同的代碼,zepto會(huì)點(diǎn)透而fastclick不會(huì)呢?
原因是zepto的代碼里面有個(gè)settimeout,而就算在這個(gè)代碼里面執(zhí)行e.preventDefault()也不會(huì)有用
這就是根本區(qū)別,因?yàn)閟ettimeout會(huì)將優(yōu)先級(jí)較低
有了定期器,當(dāng)代碼執(zhí)行到setTimeout的時(shí)候, 就會(huì)把這個(gè)代碼放到JS的引擎的最后面
而我們代碼會(huì)馬上檢測(cè)到e.preventDefault,一旦加入settimeout,e.preventDefault便不會(huì)生效,這是zepto點(diǎn)透的根本原因
結(jié)語(yǔ)
雖然,這次走了很多彎路,但是最后終于解決了問(wèn)題
相關(guān)文章
使用Vue簡(jiǎn)單實(shí)現(xiàn)一個(gè)上拉加載更多分頁(yè)組件
上拉加載更多的分頁(yè)功能大家應(yīng)該都見(jiàn)過(guò)或者使用過(guò)了吧,那么有多少同學(xué)自己實(shí)現(xiàn)過(guò)嗎,本文我們來(lái)簡(jiǎn)單實(shí)現(xiàn)一個(gè)上拉加載更多分頁(yè)組件吧2024-11-11
利用FetchEventSource在大模型流式輸出的應(yīng)用方式
這篇文章主要介紹了利用FetchEventSource在大模型流式輸出的應(yīng)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
vue使用swiper的時(shí)候第二輪事件不會(huì)觸發(fā)問(wèn)題
這篇文章主要介紹了vue使用swiper的時(shí)候第二輪事件不會(huì)觸發(fā)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
vue2.0$nextTick監(jiān)聽(tīng)數(shù)據(jù)渲染完成之后的回調(diào)函數(shù)方法
今天小編就為大家分享一篇vue2.0$nextTick監(jiān)聽(tīng)數(shù)據(jù)渲染完成之后的回調(diào)函數(shù)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
在Vue實(shí)例上掛載自己定義的工具類(lèi)的操作方法
在實(shí)際的Vue開(kāi)發(fā)中,我們經(jīng)常需要在多個(gè)組件之間共享一些工具函數(shù)或類(lèi),比如格式化日期、處理字符串、操作數(shù)組等,本文將詳細(xì)介紹如何在Vue實(shí)例上掛載自己定義的工具類(lèi),并在項(xiàng)目中高效使用這些工具,需要的朋友可以參考下2024-09-09
vue3中pinia的使用及持久化的實(shí)現(xiàn)
Pinia是一個(gè)基于Vue3的狀態(tài)管理庫(kù),本文主要介紹了vue3中pinia的使用及持久化的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
Vue中使用vee-validate表單驗(yàn)證的方法
vee validate 一個(gè)輕量級(jí)的 vue表單驗(yàn)證插件。接下來(lái)通過(guò)本文給大家分享Vue中使用vee-validate表單驗(yàn)證的方法,需要的朋友參考下吧2018-05-05
Vue前端利用slice()方法實(shí)現(xiàn)分頁(yè)器
分頁(yè)功能是常見(jiàn)的需求之一,本文主要介紹了Vue前端利用slice()方法實(shí)現(xiàn)分頁(yè)器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Vue中使用Echarts可視化圖表寬度自適應(yīng)的完美解決方案
這篇文章主要介紹了Vue中使用Echarts可視化圖表,寬度自適應(yīng)解決方案,我的解決方案是,在放置Echarts的容器(div)外層再套一層容器(div),外層容器寬度固定設(shè)置手機(jī)屏幕寬,感興趣的朋友跟隨小編一起看看吧2022-09-09

