이번에는 fastclick 소스 코드를 사용하여 탭을 구문 분석하고 fastclick 소스 코드를 사용하여 탭을 구문 분석할 때의 주의 사항을 소개하겠습니다.
최근 탭 이벤트를 사용하면서 여러 가지 문제가 발생했습니다. 문제 중 하나는 클릭 연결 문제를 해결하려면 원래 클릭을 탭으로 변경해야 한다는 것입니다. 이 경우 IE 사용자를 포기합니다.
물론 호환성은 가능하지만 누구도 이전 코드를 건드리고 싶어하지 않아서 오늘은 fastclick을 생각해냈습니다,
최근 탭스루 사건에 대해 게시한 것은 이번이 네 번째입니다. 저희는 항상 "클릭스루" 마스크 해결에 대해 걱정해왔기 때문에 오늘 사장님께서 라이브러리 패스트클릭을 제안하셨고, 마침내 문제가 해결되었습니다
그리고 클릭을 탭으로 바꿀 필요도 없으니 사장님께서 진지하게 "오해하지 말아주세요. 메일은 다 보냈어요..."라고 진지하게 말씀해주셨어요...
그래서 오후에 fastclick 라이브러리를 살펴보며 이것이 우리 문제를 해결할 수 있는지 알아보고 있었으니 시작해 보겠습니다
fastclick 소스 코드 읽기
Nima는 사용하기 너무 쉽습니다.
FastClick.attach(document.body);
라고 말하세요. 이제 모든 클릭 응답 속도가 직접적으로 향상됩니다! 어떤 입력이 포커스를 받는 문제도 해결! ! ! 젠장, 정말 그게 가능하다면 페이지를 바꾼 동료가 분명 날 물어버릴 거야
입구는 부착 방식입니다:
FastClick.attach = function(layer) { 'use strict'; return new FastClick(layer); };
. 이 형제가 방금 코드를 인스턴스화했으므로 우리는 여전히 constructor를 살펴봐야 합니다.
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; }
터치스타트 이벤트가 지원되지 않으면 true를 반환하세요
PS: 현재 제 생각에는 fastclick도 터치 이벤트로 시뮬레이션해야 하지만 클릭 연결 문제는 없습니다
나중에 안드로이드에도 몇 가지 문제점이 있다고 판단했는데 여기서는 크게 언급하지 않겠습니다. 아마도 터치를 지원해야 지원이 가능하다는 의미일 것 같아서 메인 코드로 돌아왔습니다
메인 코드에서는 브라우저가 터치 이벤트를 지원하지 않거나 다른 문제가 있는 경우 바로 팝업되는 것을 볼 수 있습니다
그런 다음 deviceIsAndroid 속성이 있습니다. 살펴보겠습니다(사실 보지 않고도 Android 기기인지 알 수 있습니다)
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 바인딩 이벤트
좋아, 이 사람이 등록 이벤트를 바인딩하기 시작했는데 지금까지 이상한 점을 발견하지 못했습니다
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);
특정 이벤트 기능은 앞에 다시 작성했습니다. 지금은 무시하고 나중에 계속 살펴보겠습니다. (즉, 이 사람은 이벤트를 충분히 바인딩했습니다.)
즉시 전파 중지
이제 속성이 하나 더 있습니다:
현재 이벤트의 버블링 동작을 방지하고 현재 이벤트가 위치한 요소에서 동일한 유형의 모든 이벤트에 대한
이벤트 처리기능이 계속 실행되는 것을 방지합니다. 요소에 동일한 유형의 이벤트에 대한 여러 이벤트 청취 기능이 있는 경우 해당 유형의 이벤트가 트리거되면 청취 기능이 실행되면 여러 이벤트 청취 기능이 순차적으로 실행됩니다. event.stopImmediatePropagation() 메서드는 차단되는 이벤트의 버블링 동작(event.stopPropagation 메서드의 역할) 외에도 해당 요소에 바인딩된 동일한 유형의 다른 수신 함수 실행도 차단됩니다.
<style> p { height: 30px; width: 150px; background-color: #ccf; } p {height: 30px; width: 150px; background-color: #cfc; } </style> <p> </p><p>paragraph</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>
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는 무엇입니까?
이러한 관점에서 Node는 우리 노드를 나타내는 시스템 속성이므로 여기서 로그아웃 이벤트를 다시 작성합니다这里,我们发现,其实他只对click进行了特殊处理
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { if (!event.propagationStopped) { callback(event); } }), capture);
其中有个hijacked劫持是干神马的就暂时不知道了,估计是在中间是否改写的意思吧
然后这里重写写了下,hijacked估计是一个方法,就是为了阻止在一个dom上注册多次事件多次执行的情况而存在的吧
注销和注册差不多我们就不管了,到此我们其实重写了我们传入dom的注册注销事件了,好像很厉害的样子,意思以后这个dom调用click事件用的是我们的,当然这只是我暂时的判断,具体还要往下读,而且我觉得现在的判断不靠谱,于是我们继续吧
我们注销事件时候可以用addEventListener 或者 dom.onclick=function(){},所以这里有了下面的代码:
if (typeof layer.onclick === 'function') { oldOnClick = layer.onclick; layer.addEventListener('click', function(event) { oldOnClick(event); }, false); layer.onclick = null; }
此处,他的主干流程居然就完了,意思是他所有的逻辑就在这里了,不论入口还是出口应该就是事件注册了,于是我们写个代码来看看
测试入口
<input> <input> $('#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) <p style="text-align: left;"> 其中用到了一个方法: </p><pre class="brush:php;toolbar:false">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) 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) <p style="text-align: left;"> 这个代码很关键,我们首次点击会执行下面的逻辑,如果连续点击就直接完蛋,下面的逻辑丫的不执行了......<br>这个不执行了,那么这个劳什子又干了什么事情呢?<br>事实上下面就没逻辑了,意思是如果确实点击过快,两次点击只会执行一次,这个阀值为200ms,这个暂时看来是没有问题的 </p><p style="text-align: left;"> 好了,我们继续往下走,于是我意识到又到了一个关键点<br>因为我们用tap事件不能使input获得焦点,但是fastclick却能获得焦点,这里也许是一个关键,我们来看看几个与获取焦点有关的函数 </p><pre class="brush:php;toolbar:false"> 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未触发就要回到上面代码了
解决“点透”(成果)
有了这个思路,我们来试试我们抽象出来的代码:
nbsp;html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <title></title> <meta> <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> <p> </p> <p> </p><p> <input> </p> <script> 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>
这样的话,便不会点透了,这是因为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标准本不支持tap事件,所以zepto tap是touchstart与touchend模拟而出 zepto在初始化时便给document绑定touch事件,在我们点击时根据event参数获得当前元素,并会保存点下和离开时候的鼠标位置 根据当前元素鼠标移动范围判断是否为类点击事件,如果是便触发已经注册好的tap事件
然后fastclick处理比较与zepto基本一致,但是又有所不同
fastclick是将事件绑定到你传的元素(一般是document.body)
② 在touchstart和touchend后(会手动获取当前点击el),如果是类click事件便手动触发了dom元素的click事件
所以click事件在touchend便被触发,整个响应速度就起来了,触发实际与zepto tap一样
好了,为什么基本相同的代码,zepto会点透而fastclick不会呢?
原因是zepto的代码里面有个settimeout,而就算在这个代码里面执行e.preventDefault()也不会有用
这就是根本区别,因为settimeout会将优先级较低
有了定期器,当代码执行到setTimeout的时候, 就会把这个代码放到JS的引擎的最后面
而我们代码会马上检测到e.preventDefault,一旦加入settimeout,e.preventDefault便不会生效,这是zepto点透的根本原因
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
Parcel.js+Vue 2.x 빠른 구성 패키징 방법
위 내용은 fastclick 소스 코드를 사용하여 탭 구문 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!