Heim > Artikel > Web-Frontend > So lösen Sie das Problem, indem Sie im Fastclick-Code auf „Durchklicken“ tippen
Dieser Artikel stellt hauptsächlich den Wissensinhalt vor, um das Tap-„Click-through“-Problem durch die Analyse des Fastclick-Quellcodes vollständig zu lösen.
Die jüngste Verwendung von Tap-Ereignissen hat mir verschiedene Probleme bereitet. Um das Problem des Durchklickens zu lösen, müssen wir die ursprünglichen Klicks in Taps umwandeln. In diesem Fall werden wir den IE aufgeben Benutzer
Natürlich ist Kompatibilität möglich, aber niemand möchte den alten Code anfassen, also haben wir uns heute das Fastclick-Ding ausgedacht
Dies ist das vierte Mal, dass wir etwas über Taps gepostet haben Wir haben kürzlich hart daran gearbeitet, das Problem zu lösen. Ich war besorgt über die „Click-through“-Maske, deshalb hat mein Chef heute einen Bibliotheks-Fastclick vorgeschlagen, der sich schließlich als Lösung für unser Problem erwiesen hat musste nicht durch tap ersetzt werden, also sagte unser Chef im Ernst zu mir: Bitte verstehen Sie mich nicht falsch, ich habe alle E-Mails gesendet...
Also habe ich mir am Nachmittag die Fastclick-Bibliothek angesehen Um zu sehen, ob es unser Problem lösen könnte, haben wir angefangen. Lass uns
den Fastclick-Quellcode lesenEs ist so einfach zu verwenden, sag einfach:
FastClick.attach(document.body);
Alle Klick-Reaktionsgeschwindigkeiten werden also gerade jetzt direkt verbessert! Das Problem, welche Eingabe den Fokus erhält, ist ebenfalls gelöst! ! ! Verdammt, wenn es wirklich möglich ist, wird mir der Kollege, der die Seite geändert hat, auf jeden Fall das Leben schwer machen
Schritt für Schritt folgen wir, der Eingang ist die Attach-Methode:
FastClick.attach = function(layer) { 'use strict'; return new FastClick(layer); };
Dieser Bruder ist gerade der nächste Code, daher müssen wir uns auch unseren Konstruktor ansehen:
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; } }
Sehen Sie sich diesen Code an, ich weiß nicht, was viele der oben genannten Attribute bewirken ... also habe ich
if (!layer || !layer.nodeType) { throw new TypeError('Layer must be a document node'); }Hier ist zu beachten, dass wir einen Knoten an den Konstruktor übergeben müssen, sonst treten Probleme auf Dann registriert dieser Typ einige grundlegende Mausereignisse in seinen eigenen Attributmethoden, insbesondere Ganshen. Lass uns darüber reden es später Es gibt eine notNeeded-Methode auf der Rückseite:
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; };Diese Methode wird verwendet, um festzustellen, ob Fastclick benötigt wird. Schauen wir uns das an CodeErster Satz:
if (typeof window.ontouchstart === 'undefined') { return true; }Wenn das Touchstart-Ereignis nicht unterstützt wird, geben Sie true zurück
PS: Mein aktueller Eindruck ist, dass Fastclick auch durch Touch-Ereignisse simuliert werden sollte, aber das ist nicht der Fall Habe ein Click-Through-Problem
Später haben wir auch einige Probleme von Android beurteilt, auf die ich hier nicht eingehen werde. Das bedeutet, dass es nur Touch unterstützen sollte, also zurück zum Hauptcode
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);Die spezifische Ereignisfunktion wurde vorne neu geschrieben und wir ignorieren sie vorerst und fahren fort um nach hinten zu schauen (mit anderen Worten, dieser Typ hat genügend Ereignisse gebunden)stopImmediatePropagationEs gibt noch ein weiteres Attribut: Verhindert das Blasenverhalten des aktuellen Ereignisses und verhindert die fortgesetzte Ausführung von Ereignishandlern aller Ereignisse desselben Typs auf dem Element, in dem sich das aktuelle Ereignis befindet.
Wenn ein Element über mehrere Ereignisüberwachungsfunktionen für denselben Ereignistyp verfügt, wenn ein Ereignis von Wenn dieser Typ ausgelöst wird, werden die mehreren Ereignisüberwachungsfunktionen nacheinander ausgeführt. Wenn eine Überwachungsfunktion zusätzlich zum Bubbling-Verhalten des blockierten Ereignisses (die Rolle der event.stopPropagation-Methode) ausgeführt wird, Die Ausführung anderer an das Element gebundener Überwachungsfunktionen derselben Art von Ereignissen wird ebenfalls blockiert.
<html> <head> <style> p { height: 30px; width: 150px; background-color: #ccf; } p {height: 30px; width: 150px; background-color: #cfc; } </style> </head> <body> <p> <p>paragraph</p> </p> <script> document.querySelector("p").addEventListener("click", function(event) { alert("我是p元素上被绑定的第一个监听函数"); }, false); document.querySelector("p").addEventListener("click", function(event) { alert("我是p元素上被绑定的第二个监听函数"); event.stopImmediatePropagation(); //执行stopImmediatePropagation方法,阻止click事件冒泡,并且阻止p元素上绑定的其他click事件的事件监听函数的执行. }, false); document.querySelector("p").addEventListener("click", function(event) { alert("我是p元素上被绑定的第三个监听函数"); //该监听函数排在上个函数后面,该函数不会被执行. }, false); document.querySelector("p").addEventListener("click", function(event) { alert("我是p元素,我是p元素的上层元素"); //p元素的click事件没有向上冒泡,该函数不会被执行. }, 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); } }; }Dann hat dieser Typ die Methode zum Registrieren und Abbrechen von Ereignissen neu definiert. Schauen wir uns das an Zuerst das Registrierungsereignis, das den addEventListener von Node verwendet. Was ist dieser Node? Aus dieser Sicht ist Node eine Systemeigenschaft, die unseren Knoten darstellt, daher wird das Logout-Ereignis hier neu geschrieben Hier stellen wir fest, dass es sich tatsächlich nur auf den Klick spezialisiert hat. Umgang mit
rrree
Darunter ist ein entführter Entführer. Ich vermute, dass er in der Mitte umgeschrieben wurde existiert, um zu verhindern, dass mehrere Ereignisse auf einem Dom registriert und mehrmals ausgeführt werden. Die Stornierung und Registrierung ist uns egal. An dieser Stelle haben wir die Registrierungs- und Stornierungsereignisse, die wir übergeben haben, neu geschrieben dom. Es scheint, dass das DOM in Zukunft unser Klick-Ereignis verwenden wird. Darüber hinaus muss ich mehr über die Details erfahren , also machen wir weiter. Wenn wir das Ereignis absagen, können wir addEventListener oder dom.onclick=function(){} verwenden, also hier ist der folgende Code:adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { if (!event.propagationStopped) { callback(event); } }), capture);
Testeingang
rrreeSehen wir uns diesen Haltepunkt an. Sehen Sie, was wir nach dem Klicken getan haben. Nachdem wir nun auf Schaltfläche 1 geklickt haben, wird ein Ereignis für Schaltfläche 2 registriert:
但是很遗憾,我们在电脑上不能测试,所以增加了我们读代码的困难,在手机上测试后,发现按钮2响应很快,但是这里有点看不出问题
最后alert了一个!Event.prototype.stopImmediatePropagation发现手机和电脑都是false,所以我们上面搞的东西暂时无用
FastClick.prototype.onClick = function (event) { 'use strict'; var permitted; alert('终于尼玛进来了'); 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; };
然后我们终于进来了,现在我们需要知道什么是trackingClick 了
/** * Whether a click is currently being tracked. * @type Boolean */ this.trackingClick = false;
我们最初这个属性是false,但是到这里就设置为true了,就直接退出了,说明绑定事件终止,算了这个我们暂时不关注,我们干点其它的,
因为,我觉得重点还是应该在touch事件上
PS:到这里,我们发现这个库应该不只是将click加快,而是所有的响应都加快了
我在各个事件部分log出来东西,发现有click的地方都只执行了touchstart与touchend,于是至此,我觉得我的观点成立
他使用touch事件模拟量click,于是我们就只跟进这一块就好:
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; };
其中用到了一个方法:
FastClick.prototype.getTargetElementFromEventTarget = function (eventTarget) { 'use strict'; if (eventTarget.nodeType === Node.TEXT_NODE) { return eventTarget.parentNode; } return eventTarget; };
他是获取我们当前touchstart的元素
然后将鼠标的信息记录了下来,他记录鼠标信息主要在后面touchend时候根据x、y判断是否为click
是ios情况下还搞了一些事情,我这里跳过去了
然后这里记录了一些事情就跳出去了,没有特别的事情,现在我们进入我们的出口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; };
这个家伙洋洋洒洒干了许多事情
这里纠正一个错误,他onclick那些东西现在也执行了......可能是我屏幕有变化(滑动)导致
if ((event.timeStamp - this.lastClickTime) < 200) { this.cancelNextClick = true; return true; }
这个代码很关键,我们首次点击会执行下面的逻辑,如果连续点击就直接完蛋,下面的逻辑丫的不执行了......
这个不执行了,那么这个劳什子又干了什么事情呢?
事实上下面就没逻辑了,意思是如果确实点击过快,两次点击只会执行一次,这个阀值为200ms,这个暂时看来是没有问题的
好了,我们继续往下走,于是我意识到又到了一个关键点
因为我们用tap事件不能使input获得焦点,但是fastclick却能获得焦点,这里也许是一个关键,我们来看看几个与获取焦点有关的函数
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是我们的关键,也许他是这样获取焦点的......具体我还要下来测试,留待下次处理吧
然后下面如果时间间隔过长,代码就不认为操作的是同一dom结构了
最后迎来了本次的关键:sendClick,无论是touchend还是onMouse都会汇聚到这里
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); };
他创建了一个鼠标事件,然后dispatchEvent事件(这个与fireEvent类似)
//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); //调用document对象的 createEvent 方法得到一个event的对象实例。 var event = document.createEvent('HTMLEvents'); // initEvent接受3个参数: // 事件类型,是否冒泡,是否阻止浏览器的默认行为 event.initEvent("ondataavailable", true, true); event.eventType = 'message'; //触发document上绑定的自定义事件ondataavailable document.dispatchEvent(event); var event1 = document.createEvent('HTMLEvents'); event1.initEvent("click", true, true); event1.eventType = 'message'; //触发obj元素上绑定click事件 document.getElementById("test").onclick = function () { obj.dispatchEvent(event1); };
至此,我们就知道了,我们为dom先绑定了鼠标事件,然后touchend时候触发了,而至于为什么本身注册的click未触发就要回到上面代码了
解决“点透”(成果)
有了这个思路,我们来试试我们抽象出来的代码:
<!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; } p { display: block; border: 1px solid black; height: 300px; width: 100%; } #input { width: 80px; height: 200px; display: block; } </style> </head> <body> <p> </p> <p> <p> <input type="text" /> </p> </p> <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>
这样的话,便不会点透了,这是因为zepto touch事件全部绑定值document,所以 e.preventDefault();无用
结果我们这里是直接在dom上,e.preventDefault();
便起了作用不会触发浏览器默认事件,所以也不存在点透问题了,至此点透事件告一段落......
帮助理解的图
代码在公司写的,回家后不知道图上哪里了,各位将就看吧
为什么zepto会点透/fastclick如何解决点透
我最开始就给老大说zepto处理tap事件不够好,搞了很多事情出来
因为他事件是绑定到document上,先touchstart然后touchend,根据touchstart的event参数判断该dom是否注册了tap事件,有就触发
于是问题来了,zepto的touchend这里有个event参数,我们event.preventDefault(),这里本来都是最上层了,这就代码压根没什么用
但是fastclick处理办法不可谓不巧妙,这个库直接在touchend的时候就触发了dom上的click事件而替换了本来的触发时间
意思是原来要350-400ms执行的代码突然就移到了50-100ms,然后这里虽然使用了touch事件但是touch事件是绑定到了具体dom而不是document上
E.preventDefault ist also effektiv und kann Blasenbildung und Browser-Standardereignisse verhindern. Das ist die Essenz von Fastclick, was nicht schlecht ist. ! !
Das Lesen des gesamten Fastclick-Codes ist erstaunlich, ich habe heute viel gewonnen, ich werde es hier aufzeichnen
Postscript
Mit der obigen Aussage stimmt etwas nicht, bitte korrigieren Sie es it:
Zuerst kehren wir zur ursprünglichen Zepto-Lösung zurück und sehen, welche Probleme sie hat:
Da der js-Standard keine Tap-Ereignisse unterstützt, wird Zepto Tap von Touchstart und simuliert touchend. Bindet das Touch-Ereignis an das Dokument, ruft beim Klicken das aktuelle Element ab und speichert die Mausposition anhand der Mausbewegung Wenn ja, wird das registrierte Tap-Ereignis ausgelöst.
Dann ist die Fastclick-Verarbeitung im Grunde die gleiche wie bei Zepto, aber anders.
Fastclick bindet das Ereignis an das von Ihnen übergebene Element (normalerweise document.body)
② Nach Touchstart und Touchend (der aktuelle Klick el wird manuell abgerufen) wird, wenn es sich um ein Klickereignis handelt, das Klickereignis des Dom-Elements manuell ausgelöst
, also wird das Klickereignis beim Touchend ausgelöst und die gesamte Reaktionsgeschwindigkeit erhöht sich, der Auslöser ist eigentlich der gleiche wie bei Zepto-Tap
Okay, warum ist das im Grunde derselbe Code, Zepto? kann sich durchklicken, Fastclick aber nicht?
Der Grund dafür ist, dass es im Zepto-Code ein Settimeout gibt und selbst die Ausführung von e.preventDefault() in diesem Code nicht sinnvoll ist.
Dies ist der grundlegende Unterschied, da Settimeout das priorisiert höher Niedrig
Wenn der Code mit dem Timer ausgeführt wird, um setTimeout festzulegen, wird dieser Code am Ende der JS-Engine platziert
und unser Code erkennt e.preventDefault sofort, sobald settimeout hinzugefügt wird, wird e.preventDefault nicht wirksam.
Fazit
Das Obige ist das, was ich für alle zusammengestellt habe in der Zukunft.
Verwandte Artikel:
Verwenden von GM-Cropping zum Synthetisieren von Bildern unter Nodejs
So implementieren Sie den Baidu-Index-Crawler mithilfe der Puppeteer-Bilderkennungstechnologie
Das obige ist der detaillierte Inhalt vonSo lösen Sie das Problem, indem Sie im Fastclick-Code auf „Durchklicken“ tippen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!