ホームページ > 記事 > ウェブフロントエンド > fastclickコードのタップ「クリックスルー」を解決する方法
この記事は主に、fastclick ソースコード解析を通じてタップ「クリックスルー」問題を完全に解決するための知識コンテンツを紹介します。興味のある友人はそれについて学ぶことができます。
最近のタップ イベントの使用により、さまざまな問題が発生しました。問題の 1 つは、問題を解決するには、元のクリックをタップに変更する必要があるということです。この場合、もちろん、IE ユーザーは放棄されます。互換性を保つことはできますが、誰も古いコードに触れたくなかったので、今日は fastclick について思いつきました
私たちはタップスルー事件について最近投稿するのが 4 回目です。今日、上司は fastclick と呼ばれるライブラリを提案しました。これが最終的に私たちの問題を解決することが証明されました
そして、click を Tap に置き換える必要はありません。そこで、上司は私に、やめてくださいと真剣に言いました。誤解しないでください。私はすべてのメールを送信しました。....
それで、私は午後に fastclick ライブラリを調べて、問題を解決できるかどうかを確認しました。それでは、始めましょう
fastclick のソース コードを読んでください。ニマ、使い方が簡単すぎるので、こう言うだけです:
FastClick.attach(document.body);
つまり、すべてのクリックの応答速度が直接改善されます、今だけ!どの入力がフォーカスを取得するかの問題も解決されます。 ! !くそー、もしそれが本当に可能なら、ページを変更した同僚は間違いなく私を苦労させるでしょう
段階的に、私たちは従います、入り口はアタッチメソッドです:
FastClick.attach = function(layer) { 'use strict'; return new FastClick(layer); };
この兄弟はコードをインスタンス化したばかりなので、コンストラクター:
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'); }
をノードに渡す必要があることに注意してください。それ以外の場合は問題が発生します
その後、この人は独自の属性メソッドにいくつかの基本的なマウス イベントを登録します
後ろに 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; };
このメソッドを使用します。 fastclick が必要かどうかを判断するためのコードです。最初の文を見てみましょう:
if (typeof window.ontouchstart === 'undefined') { return true; }
touchstart イベントがサポートされていない場合は、true を返します
PS: 私の現在の感覚では、fastclick です。タッチイベントもシミュレートされていますが、ポイントスルーの問題はありませんAndroidの問題も後ほど判断しますので、ここでは無視します。タッチのみをサポートする必要があることを意味します。メイン コードに戻ります
メイン コードでは、ブラウザがタッチ イベントやその他の問題をサポートしていない場合は、直接ポップアップ表示されます
次に、deviceIsAndroid 属性があります。見てみましょう (実際には、見なくても Android デバイスかどうかわかります)
FastClick.prototype .deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
Binding events
さて、この人は登録イベントをバインドしてみましたが、今のところ何もおかしなことはありません
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);
特定のイベント関数は にあります 前は書き換えられているので、とりあえず無視して後ろを見続けます (ちなみに、この人は十分にバインドしています) events)
stopImmediatePropagation
もう 1 つの属性があります:
現在のイベントのバブリング動作を停止し、現在のイベントが配置されている要素上の同じタイプのすべてのイベントに対するイベント ハンドラーの継続実行を防止します。
要素に同じ種類のイベントに対する複数のイベント リスナー関数がある場合、この種類のイベントがトリガーされると、リスニング関数に加えて、event.stopImmediatePropagation() メソッドが実行されると、複数のイベント リスナー関数が順番に実行されます。ブロックされているイベントのバブリング動作 (event.stopPropagation メソッドの役割)、要素にバインドされている同じタイプの他のイベントもブロックされます
<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); } }; }
次に、この人はメソッドを再定義しました。イベントの登録と登録解除の説明
まず、Node の addEventListener を使用する登録イベントを見てみましょう。
この観点から見ると、Node はノードを表すシステム属性であるため、ログアウト イベントがここで書き換えられます
ここで、実際にはクリック時にのみ特別な処理が実行されることがわかります
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { if (!event.propagationStopped) { callback(event); } }), capture);
そのうちの 1 つがハイジャックされていますハイジャックの目的は分かりませんが、おそらく途中で書き換えるということなのでしょうか。状況の説明
この時点では、dom に渡した登録とログアウトのイベントを実際に書き換えました。これは、将来的には dom が強力になることを意味します。クリックイベントを使用してクリックイベントを呼び出します。もちろん、これは単なる私の一時的な判断です。詳細については続きを読む必要があります。現在の判断は信頼できないと思うので、続行しましょう
イベントをキャンセルするときは、 addEventListener または dom.onclick=function(){ } を使用できるので、次のコードを示します。
if (typeof layer.onclick === 'function') { oldOnClick = layer.onclick; layer.addEventListener('click', function(event) { oldOnClick(event); }, false); layer.onclick = null; }ここで、彼のメイン プロセスは実際に完了しています。これは、入り口または出口に関係なく、すべてのロジックがここにあることを意味します。イベントを登録する必要があるので、コードを記述します。テストの入り口を見てみましょう
<input type="button" value="addevent"> <input type="button" value="addevent1"> $('#addEvent').click(function () { var dom = $('#addEvent1')[0] dom.addEventListener('click', function () { alert('') var s = ''; }) });このブレークポイントを使用して、ボタン 1 をクリックした後に何が行われたかを確認してください。ボタンにイベントが登録されます。 2:
但是很遗憾,我们在电脑上不能测试,所以增加了我们读代码的困难,在手机上测试后,发现按钮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 はバブリングとブラウザのデフォルト イベントを防ぐことができます。これは、fastclick の本質であり、悪いことではありません。 ! !
fastclick コード全体を読むのは驚くべきことです。今日は多くのことを得ることができました。ここに記録します
追記
上記の記述には何か間違っている点があります。修正させてください:
まず、最初に戻りましょう。オリジナルの zepto プランを見て、その内容を確認してください。 問題:
js 標準はタップ イベントをサポートしていないため、Zepto のタップは、初期化時にタッチ イベントをドキュメントにバインドします。イベントパラメータに従って現在の要素を取得し、ポイントを保存します。下に移動するときと離れるときのマウスの位置は、現在の要素のマウスの移動範囲に応じてクリックイベントであるかどうかを判断します。その場合、登録されたタップイベントがトリガーされます
。その後、fastclickの処理は基本的にzeptoと同じですが、違います
fastclickは渡した要素(通常はdocument.body)にイベントをバインドします
② touchstartとtouchendの後(現在のクリックelは手動で取得されます) , クリックイベントの場合は、dom要素のクリックイベントが手動でトリガーされます
それで、タッチエンドでクリックイベントがトリガーされ、全体の応答速度が上がります
トリガーは実際にはゼプトタップと同じです
基本的に同じコードで、zepto はクリックスルーできるのに、fastclick はクリックスルーできないのはなぜですか?
その理由は、zeptoのコードにsettimeoutがあり、このコードでe.preventDefault()が実行されても機能しないためです
これが根本的な違いであり、settimeoutの優先順位が低くなるためです
とタイマー、コードが setTimeout に実行されると、このコードは JS エンジンの最後に配置されます
そして、私たちのコードはすぐに e.preventDefault を検出します。 settimeout が追加されると、e.preventDefault は有効になりません。ゼプトポイント 問題の根本原因
結論
以上が皆様の参考になれば幸いです。
GM Croppingを使用してNodejsで画像を合成するjsを使用してjsonを呼び出す方法Puppeteer画像認識技術を使用してBaiduインデックスクローラーを実装する方法🎜🎜以上がfastclickコードのタップ「クリックスルー」を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。