https://segmentfault.com/a/1190000003848737

Ready to upload

The phenomenon of penetration is inseparable from the click delay solution. To understand the phenomenon of penetration, you need to understand the click delay solution principle.

Click events on the mobile end are delayed by 300ms.

In the safar browser of the earliest iPhone, in order to realize the double-click magnification effect on the touch screen, when the user clicks the screen, it will judge whether there is a second click within 300ms. If there is, it will be interpreted as double-click. If there is no, it is a click, and the click event will be triggered. When you click on the screen of a mobile device, it can be broken down into multiple events, in the following order:

Touchstart — touchMove — touchEnd — click,

Solutions to delays:

Touchstart Touchend is non-delayed and can trigger the event that the user wants to trigger at Click when touchEnd is triggered.

How Zepto solves click latency:

Custom tap event. When the user clicks on the element, the Touchend event occurs first, and when the TouchEnd event bubbles up into the Document, the tap event bound to the target element is triggered

Simply simulate the implementation of Zepto Tap (ignore the judgment of touchstart and Touchend click positions) :

// Bind the Touchend event to the document element, customize the tap event in the Touchend event handler, and when the touchend event of the clicked target element bubbles on the document, Trigger binding document tap events on the target element. The addEventListener ('touchend'.function(e) {var evt = document.createEvent('Event'); Evt. Init (" tap ",true.true); // Trigger the tap event bound to the target element e.target.dispatch(evt); },false); / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / users binding tap event Document. The getElementById (" elementid "). The addEventListener ('tap ', function(e) {// click event logic}, false);Copy the code

Zepto tap penetration phenomenon:

Source of the touch event

Most of the action on a PC web page is mouse-based, that is, in response to mouse events, including mousedown, Mouseup, Mousemove, and Click events. Click action, event trigger process is: mousedown -> mouseup -> click three steps.

The phone doesn’t have a mouse, so touch events are used to do something similar. Touch events include TouchStart, TouchMove, and Touchend. Note that there is no tap event on the phone. The finger triggers the touch event as follows: TouchStart -> TouchMove -> TouchEnd.

The lack of a mouse on the phone does not mean that the phone cannot respond to mouse events. Someone has done a comparative experiment on events on PC and mobile phone to show that mobile phone responds to touch events faster than mouse events.


<! 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"< span style = "box-sizing: border-box! Important; word-wrap: break-word! Important;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"> The underlying element </div> <div id="popupLayer">
			<div class="layer-title"> pop-up layer </div> <div class="layer-action">
				<button class="btn" id="closePopup"</button> </div> </div> <div id="bgMask"></div>


	<script type="text/javascript" src=""></script>
	<script type="text/javascript">
	Zepto(function($){// Click through 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>
</html>
Copy the code

There is a label bound to tap event in the mask layer, and the mask layer disappears when triggered. There is a button bound to Click right below the label, and click on the upper label will also trigger the click event of the lower element, causing penetration.

Why does penetration occur:

Combined with the principle of the previous TAP event to analyze:

When the tap event is triggered and the upper mask layer is closed, the event only proceeds to TouchEnd, while click is triggered about 300ms later. When click is triggered, the upper mask layer has disappeared, which is equivalent to clicking on the lower element.

What elements below form penetration:

As a matter of principle, because penetration occurs when click occurs, that is, the element underlying the click event or focus Focusout, or the tag element that defaults when clicked, For example, a input(will be the type of the system keyboard or bound to the focus event).

How to solve penetration:

Method 1: Replace the tap event with the click event (300ms delay)

Method 2: Block the click event before it is triggered, such as using e.preventDefault() in the Touchend event to block subsequent click events

Why doesn’t Zepto use e.preventDefault() to solve the penetration problem?

Because the zepto tap event is always triggered when the document touches end, if you use e.preventDefault(), then all elements on the page that are triggered after the touchend will not be executed.



This is the fourth time that we have recently posted about tap penetration events. We have been frustrated with the solution of “tap penetration” mask, so today the boss proposed a library fastclick, which finally proved to solve our problem

And click need not replace for tap, so our boss on the earnest and earnest of the long said to me, you miss me, my mail are sent to……

So I spent the afternoon looking at the FastClick library to see if it would solve our problem, so here we go

Read fastclick source

It’s too easy to use.

FastClick.attach(document.body);Copy the code

So all click response speed increases directly, just now! The issue of what input gets focus has also been resolved!! Nima if really can, the original page of the colleagues will eat me

Step by step, we’ll follow and the entrance is attach method:

1 FastClick.attach = function(layer) {
2  'use strict';
3  returnnew FastClick(layer); 4};Copy the code

This sibling just instantiates the code, so we need to look at our constructor:

 1 function FastClick(layer) {
 2  'use strict';
 3  var oldOnClick, self = this;
 4  this.trackingClick = false;
 5  this.trackingClickStart = 0;
 6  this.targetElement = null;
 7  this.touchStartX = 0;
 8  this.touchStartY = 0;
 9  this.lastTouchIdentifier = 0;
10  this.touchBoundary = 10;
11  this.layer = layer;
12  if(! layer || ! layer.nodeType) { 13 throw new TypeError('Layer must be a document node');
14  }
15  this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); };
16  this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); };
17  this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); };
18  this.onTouchMove = function() { return FastClick.prototype.onTouchMove.apply(self, arguments); };
19  this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); };
20  this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); };
21  if (FastClick.notNeeded(layer)) {
22   return; 23} 24if (this.deviceIsAndroid) {
25   layer.addEventListener('mouseover', this.onMouse, true);
26   layer.addEventListener('mousedown', this.onMouse, true);
27   layer.addEventListener('mouseup', this.onMouse, true);
28  }
29  layer.addEventListener('click', this.onClick, true);
30  layer.addEventListener('touchstart', this.onTouchStart, false);
31  layer.addEventListener('touchmove', this.onTouchMove, false);
32  layer.addEventListener('touchend', this.onTouchEnd, false);
33  layer.addEventListener('touchcancel', this.onTouchCancel, false); 34, 35if(! Event.prototype.stopImmediatePropagation) { 36 layer.removeEventListener =function(type, callback, capture) {
37    var rmv = Node.prototype.removeEventListener;
38    if (type= = ='click') {
39     rmv.call(layer, type, callback.hijacked || callback, capture); 40}else {
41     rmv.call(layer, type, callback, capture); 43 42}}; 44 45 layer.addEventListener =function(type, callback, capture) {
46    var adv = Node.prototype.addEventListener;
47    if (type= = ='click') {
48     adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
49      if (!event.propagationStopped) {
50       callback(event);
51      }
52     }), capture);
53    } else {
54     adv.call(layer, type, callback, capture); 55} 56}; 57} 58if (typeof layer.onclick === 'function') {
59   oldOnClick = layer.onclick;
60   layer.addEventListener('click'.function(event) { 61 oldOnClick(event); 62},false); 63 layer.onclick = null; 65 64}}Copy the code

Look at this code, many of the above attributes do what I do not know…… So we ignore

1 if(! layer || ! layer.nodeType) { 2 throw new TypeError('Layer must be a document node'); 3}Copy the code

One of the things to note here is that we must pass a node to the constructor, otherwise we will have a problem

This guy then registers some basic mouse events with his property methods. What does that do

There is a notNeeded method for the back point:

 1 FastClick.notNeeded = function(layer) {
 2  'use strict';
 3  var metaViewport;
 4  if (typeof window.ontouchstart === 'undefined') {5return true; 6} 7if ((/Chrome\/[0-9]+/).test(navigator.userAgent)) {
 8   if (FastClick.prototype.deviceIsAndroid) {
 9    metaViewport = document.querySelector('meta[name=viewport]');
10    if (metaViewport && metaViewport.content.indexOf('user-scalable=no')! = = 1) {11return true; 13 12}}else{14return true; 15} 16} 17if (layer.style.msTouchAction === 'none'18) {return true; 19} 20return false; 21};Copy the code

Fastclick: fastClick: fastClick: fastClick: fastClick: fastClick: fastClick: fastClick

First sentence:

1 if (typeof window.ontouchstart === 'undefined'2) {return true; 3}Copy the code

If the TouchStart event is not supported, return true

Support for Touch is the only way to support android, so back to the trunk code

In the trunk code, we see that if the browser doesn’t support touch events or other issues, it just pops out

And then there’s a deviceIsAndroid property in there, so let’s go check it out.

FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf(‘Android’) > 0;

The binding event

Okay, so this guy is binding registration events, so far nothing’s wrong

 1 if (this.deviceIsAndroid) {
 2  layer.addEventListener('mouseover', this.onMouse, true);
 3  layer.addEventListener('mousedown', this.onMouse, true);
 4  layer.addEventListener('mouseup', this.onMouse, true);
 5 }
 6 layer.addEventListener('click', this.onClick, true);
 7 layer.addEventListener('touchstart', this.onTouchStart, false);
 8 layer.addEventListener('touchmove', this.onTouchMove, false);
 9 layer.addEventListener('touchend', this.onTouchEnd, false);
10 layer.addEventListener('touchcancel', this.onTouchCancel, false);Copy the code

The specific event function was overwritten earlier, so let’s leave it at that and move on.

stopImmediatePropagation

Finished with an additional attribute:

Prevents the bubbling behavior of the current event and prevents the continuation of all events of the same type on the element of the current event.

If an element has more than one event listener of the same type, the event listeners are executed in sequence when an event of that type is fired. . If a monitor function performs the event stopImmediatePropagation () method, which is in addition to the event bubbling behaviors is prevented (event. The role of stopPropagation method), the element is bound to the rest of the events of the same type of surveillance function execution will be stopped.

 1 <html>
 2     <head>
 3         <style>
 4             p { height: 30px; width: 150px; background-color: #ccf; }
 5             div {height: 30px; width: 150px; background-color: #cfc; }
 6         </style>
 7     </head>
 8     <body>
 9         <div>
10             <p>paragraph</p>
11         </div>
12         <script>
13             document.querySelector("p").addEventListener("click".function(event)
14             {
15                 alert("I'm the first listener to be bound to the p element."); 16},false);
17             document.querySelector("p").addEventListener("click".function(event)
18             {
19                 alert("I'm the second listener bound to the p element."); 20 event.stopImmediatePropagation(); 21 // Implement the stopImmediatePropagation method to prevent the click events from bubbling and to prevent the events of other click events bound to the P element from listening to the function.false);
23             document.querySelector("p").addEventListener("click".function(event)
24             {
25                 alert("I'm the third listener bound to the p element."); // This function will not be executed.false);
28             document.querySelector("div").addEventListener("click".function(event)
29             {
30                 alert("I'm a div element, I'm a superelement of a P element."); 31 // the p element's click event does not bubble up, and the function will not be executed.false);
33         </script>
34     </body>
35 </html>Copy the code

 1 if(! Event.prototype.stopImmediatePropagation) { 2 layer.removeEventListener =function(type, callback, capture) {
 3   var rmv = Node.prototype.removeEventListener;
 4   if (type= = ='click') {
 5    rmv.call(layer, type, callback.hijacked || callback, capture); 6}else {
 7    rmv.call(layer, type, callback, capture); 8}}; 10 11 layer.addEventListener =function(type, callback, capture) {
12   var adv = Node.prototype.addEventListener;
13   if (type= = ='click') {
14    adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
15     if (!event.propagationStopped) {
16      callback(event);
17     }
18    }), capture);
19   } else {
20    adv.call(layer, type, callback, capture); 21}}; 23}Copy the code

And then this guy redefines how to register and unregister events,

Let’s first look at the registration event, which uses the addEventListener of Node. What is this Node?

Node is a system property that represents our Node, so we override the logout event

Here, we see that he actually only does special processing for Click

1 adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
2  if(! event.propagationStopped) { 3 callback(event); 4 } 5 }), capture);Copy the code

This is one of these hijacked events which is really unknown, this is really a long hijacked tour of duty and this is really a way of preventing this event from being registered many times on a DOM and executed many times each long time

Logout and registered almost we have no matter, here we rewrite the registration cancellation we introduced into the dom events, looks very strong, meaning the dom calls after click event with us, of course this is only my temporary judgment, specific reading even, and I think now the judgment of the unreliable, so we go ahead

We can unregister events using addEventListener or dom.onclick=function(){}, so we have the following code:

1 if (typeof layer.onclick === 'function') {
2  oldOnClick = layer.onclick;
3  layer.addEventListener('click'.function(event) { 4 oldOnClick(event); 5},false); 6 layer.onclick = null; 7}Copy the code

At this point, his trunk process is dead, meaning that all his logic is here, whether the entry or exit should be the event registration, so let’s write a code to see

Test the entrance

 1 <input type="button" id="addEvent" value="addevent">
 2 <input type="button" id="addEvent1" value="addevent1"4 $(> 3'#addEvent').click(function () {
 5     var dom = $('#addEvent1')[0]
 6     dom.addEventListener('click'.function () {
 7         alert(' ')
 8         var s = ' '; 9 10}}));Copy the code

Let’s go to this breakpoint to see what we did when we clicked, we now click button 1 to register the event for button 2:

Unfortunately, we could not test it on the computer, which made it more difficult for us to read the code. After testing it on the mobile phone, we found that button 2 responded quickly, but there was something wrong here. Finally, we alerted one of them! Event. The prototype. StopImmediatePropagation found that cell phones and computers are all false, so we make above temporarily useless things

 1 FastClick.prototype.onClick = function (event) {
 2     'use strict';
 3     var permitted;
 4     alert('I finally got the fuck in here.');
 5     if (this.trackingClick) {
 6         this.targetElement = null;
 7         this.trackingClick = false;
 8         return true; 9} 10if (event.target.type === 'submit' && event.detail === 0) {
11         return true;
12     }
13     permitted = this.onMouse(event);
14     if (!permitted) {
15         this.targetElement = null;
16     }
17     returnpermitted; 18};Copy the code

And then we finally get in, and now we need to know what trackingClick is

1 /**
2 * Whether a click is currently being tracked.
3 * @type boolean
4 */
5 this.trackingClick = false;Copy the code

We originally set this property to false, but now we’ve set it to true, and we’re just going to exit, which means that the binding event is terminated, but let’s not focus on that for now, let’s do something else, because I think the focus should be on the touch event

PS: At this point, we see that the library should speed up not just click, but all responses

I log things in each event section and only execute touchStart and TouchEnd where there is click, so I think my point is valid. He uses the touch event to simulate the amount of click, so we just follow this one:

 1 FastClick.prototype.onTouchStart = function (event) {
 2     'use strict';
 3     var targetElement, touch, selection;
 4     log('touchstart');
 5     if (event.targetTouches.length > 1) {
 6         return true;
 7     }
 8     targetElement = this.getTargetElementFromEventTarget(event.target);
 9     touch = event.targetTouches[0];
10     if (this.deviceIsIOS) {
11         selection = window.getSelection();
12         if(selection.rangeCount && ! selection.isCollapsed) { 13return true; 14} 15if(! this.deviceIsIOS4) { 16if (touch.identifier === this.lastTouchIdentifier) {
17                 event.preventDefault();
18                 return false;
19             }
20             this.lastTouchIdentifier = touch.identifier;
21             this.updateScrollParent(targetElement);
22         }
23     }
24     this.trackingClick = true;
25     this.trackingClickStart = event.timeStamp;
26     this.targetElement = targetElement;
27     this.touchStartX = touch.pageX;
28     this.touchStartY = touch.pageY;
29     if((event.timeStamp - this.lastClickTime) < 200) { 30 event.preventDefault(); 31} 32return true; 33};Copy the code

One method was used:

1 FastClick.prototype.getTargetElementFromEventTarget = function (eventTarget) {
2     'use strict';
3     if (eventTarget.nodeType === Node.TEXT_NODE) {
4         returneventTarget.parentNode; 5} 6returneventTarget; 7};Copy the code

This is the element that gets our current TouchStart

And then he records the mouse information, and he records the mouse information mainly later in the touchend based on x and Y to determine whether it’s click is ios and he does some things, I skipped over here and then recorded some things here and jumped out, nothing special, now we go to our exit, touchend

 1 FastClick.prototype.onTouchEnd = function (event) {
 2     'use strict';
 3     var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
 4     log('touchend');
 5     if(! this.trackingClick) { 6return true; 7} 8if ((event.timeStamp - this.lastClickTime) < 200) {
 9         this.cancelNextClick = true;
10         return true;
11     }
12     this.lastClickTime = event.timeStamp;
13     trackingClickStart = this.trackingClickStart;
14     this.trackingClick = false;
15     this.trackingClickStart = 0;
16     if (this.deviceIsIOSWithBadTarget) {
17         touch = event.changedTouches[0];
18         targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
19         targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
20     }
21     targetTagName = targetElement.tagName.toLowerCase();
22     if (targetTagName === 'label'23) {forElement = this.findControl(targetElement);
24         if (forElement) {
25             this.focus(targetElement);
26             if (this.deviceIsAndroid) {
27                 return false;
28             }
29             targetElement = forElement; 30}} 31else if (this.needsFocus(targetElement)) {
32         if((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top ! == window && targetTagName ==='input')) {
33             this.targetElement = null;
34             return false;
35         }
36         this.focus(targetElement);
37         if(! this.deviceIsIOS4 || targetTagName ! = ='select') { 38 this.targetElement = null; 39 event.preventDefault(); 40} 41return false; 42} 43if(this.deviceIsIOS && ! this.deviceIsIOS4) { 44 scrollParent = targetElement.fastClickScrollParent; 45if(scrollParent && scrollParent.fastClickLastScrollTop ! == scrollParent.scrollTop) { 46return true; 47} 48} 49if (!this.needsClick(targetElement)) {
50         event.preventDefault();
51         this.sendClick(targetElement, event);
52     }
53     return false; 54};Copy the code

This guy gets a lot done in a big way

Here to correct an error, he onclick those things now also execute…… Maybe my screen changes (swipes)

1 if ((event.timeStamp - this.lastClickTime) < 200) {
2  this.cancelNextClick = true;
3  return true; 4}Copy the code

This code is very key, we click the first time will execute the following logic, if the continuous click on the direct death, the following logic ya does not execute…… This is not implemented, so what did this guy do? If you do click too fast, it will only execute once in two clicks. The threshold is 200ms, which seems to be fine for now

Ok, so let’s move on, and then I realize that I’ve hit another key point because we can’t get focus for input with a tap event, but fastclick does, and this might be a key point, so let’s look at a couple of functions that have to do with getting focus

 1 FastClick.prototype.focus = function (targetElement) {
 2     'use strict';
 3     var length;
 4     if(this.deviceIsIOS && targetElement.setSelectionRange) { 5 length = targetElement.value.length; 6 targetElement.setSelectionRange(length, length); 7}else{ 8 targetElement.focus(); 9}};Copy the code

SetSelectionRange is our key, and maybe this is how it gets focus…… I’m going to test that, but I’ll do it next time and then if the time interval is too long, the code doesn’t think it’s operating on the same DOM structure and then finally comes to the key: sendClick, either Touchend or onMouse is going to converge here

 1 FastClick.prototype.sendClick = function (targetElement, event) {
 2     'use strict';
 3     var clickEvent, touch;
 4     // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (# 24)
 5     if(document.activeElement && document.activeElement ! == targetElement) { 6 document.activeElement.blur(); 7 } 8 touch = event.changedTouches[0]; 9 // Synthesise a click event, with an extra attribute so it can be tracked 10 clickEvent = document.createEvent('MouseEvents');
11     clickEvent.initMouseEvent('click'.true.true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false.false.false.false, 0, null);
12     clickEvent.forwardedTouchEvent = true; 13 targetElement.dispatchEvent(clickEvent); 14};Copy the code

He creates a mouse event and dispatchEvent event (this is similar to fireEvent)

1 / / document binding on custom ondataavailable event 2 document. AddEventListener ('ondataavailable'.function(event) { 3 alert(event.eventType); 4},false);
 5 var obj = document.getElementById("obj"); 6 // Bind the click event 7 obj. AddEventListener ('click'.function(event) { 8 alert(event.eventType); 9},false); 10 // Call the createEvent method of the Document object to get an event object instance. 11 var event = document.createEvent('HTMLEvents'); 12 // initEvent takes 3 parameters: 13 // Event type, whether to bubble, and whether to block default browser behavior"ondataavailable".true.true);
15 event.eventType = 'message'; 17 Document.dispatchEvent (event); 18 19 var event1 = document.createEvent('HTMLEvents');
20 event1.initEvent("click".true.true);
21 event1.eventType = 'message'; 23 Document.getelementById ("test").onclick = function() { 24 obj.dispatchEvent(event1); 25};Copy the code

The dom is bound to a mouse event, and the touchend is triggered

Solve “penetration” (results)

With that in mind, let’s try our abstracted code:

1 <! DOCTYPE html PUBLIC"- / / / / W3C DTD XHTML 1.0 Transitional / / EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml">
 3 <head>
 4     <title></title>
 5     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 6     <style>
 7         #list { display: block; position: absolute; top: 100px; left: 10px; width: 200px; height: 100px; }
 8         div { display: block; border: 1px solid black; height: 300px; width: 100%; }
 9         #input { width: 80px; height: 200px; display: block; }
10     </style>
11 </head>
12 <body>
13     <div id="list" style="background: gray;">
14     </div>
15     <div id="wrapper">
16         <div id="d">
17             <input type="text" id="input" />
18         </div>
19     </div>
20     <script type="text/javascript">
21         var el = null;
22         function getEvent(el, e, type) {
23             e = e.changedTouches[0];
24             var event = document.createEvent('MouseEvents');
25             event.initMouseEvent(type.true.true, window, 1, e.screenX, e.screenY, e.clientX, e.clientY, false.false.false.false, 0, null);
26             event.forwardedTouchEvent = true;
27             return event;
28         }
29         list.addEventListener('touchstart'.function (e) {
30             var firstTouch = e.touches[0]
31             el = firstTouch.target;
32             t1 = e.timeStamp;
33         })
34         list.addEventListener('touchend'.function (e) {
35             e.preventDefault();
36             var event = getEvent(el, e, 'click');
37             el.dispatchEvent(event);
38         })
39         var list = document.getElementById('list');
40         list.addEventListener('click'.function (e) {
41             list.style.display = 'none';
42             setTimeout(function () {
43                 list.style.display = ' '; 44}, 1000); 45 }) 46 </script> 47 </body> 48 </html>Copy the code

This is because zepto Touch events are all bound to document, so e.preventDefault(); The useless results here are directly in the DOM, e.preventDefault(); The browser default event is not triggered, so there is no penetration problem, so the penetration event is over……

Diagrams to help understand

The code was written in the company, and I don’t know where the map is when I get home

Why does Zepto tap through? How does FastClick fix tap through

I started by telling the boss zepto wasn’t good enough with the TAP incident, causing a lot of trouble

Since the other events are bound to the document, touchStart and then TouchEnd determine whether the DOM registers a TAP event based on the event parameter of TouchStart

The problem is, zepto touchEnd has an event, event.preventDefault(), it’s on top of everything, so it’s useless, right

But the FastClick handling is not inelegant, as the library simply fires the CLICK event on the DOM at touchend instead of the original trigger time

What that means is that the code that was supposed to be executing at 350 to 400ms is suddenly being moved to 50 to 100ms, and then you’re using touch events but the touch events are bound to the specific dom instead of the document

So e.preventDefault works, we can block bubbling, and we can block browser default events, which is the best part of FastClick.

The entire Fastclick code to read inspired, today harvest great, in this record

Afterword.

There is something wrong with the above statement, this is corrected:

First, let’s go back to the original Zepto solution and see what’s wrong with it:

  1. Since tap events are not supported by the JS standard, Zepto Tap is modeled after TouchStart and TouchEnd
  2. Zepto binds a Touch event to the document at initialization, gets the current element from the event parameter when we click, and saves the mouse position when we click and when we leave
  3. Determine whether it is a click-like event according to the current mouse movement range of the element. If it is, the registered TAP event will be triggered

The FastClick processing is similar to Zepto, but different

  1. Fastclick binds events to the element you pass (typically document.body)

② After touchStart and TouchEnd, the click event of the DOM element will be triggered manually if it is a click event

So the Click event is fired in TouchEnd, and the whole response speed is up, firing exactly the same as zepto Tap

Ok, so why does Zepto tap into basically the same code but FastClick doesn’t?

The reason is that zepto’s code has a setTimeout, and e.preventDefault() in this code would not be useful

This is the fundamental difference, because setTimeout takes a lower priority

With the scheduler, when the code reaches setTimeout, it puts the code at the end of the JS engine

Our code will immediately detect E.preventDefault. Once setTimeout is added, e.preventDefault will not take effect, which is the root cause of Zepto dot through

conclusion

Although, this time took a lot of detours, but finally solved the problem




In fact, there are a lot of articles are written, there are a lot of content I will not repeat, summarize the following points:

  1. The 300ms delay is due to the delay in processing click events caused by the browser deciding whether to single or double click

  2. The FastClick solution replaces the click event with TouchStart in combination with TouchMove and TouchEnd

  3. Zepto’s tap will “Pierce” the page because it responds to its own tap (also known as touch event) and does not intercept the original click event, causing the event to be executed twice, and the “Pierce” effect will appear when there is a mask layer. If you don’t understand, read this article about zepto’s breakdown

Years ago to explore

At that time, I had A big question that why the click event of the page below the mask layer would be triggered after the click was delayed. I clearly clicked the A button of the mask layer, but why the event of the B button of the page below would be executed. According to my initial idea, it should be continue to execute the A button event ah!! This is what I’m feeling right now


So I started to explore this question, I searched some information, basic didn’t tell the specific reason, maybe I turn on the right way, it is not found, in desperation, I can only read fastclick source to see why it did not appear this problem, and then saw sendClick code, the in the mind suddenly have a guess.

FastClick.prototype.sendClick = function(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(this.determineEventType(targetElement), true.true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false.false.false.false, 0, null);
    clickEvent.forwardedTouchEvent = true;
    targetElement.dispatchEvent(clickEvent);
    };Copy the code

Note that initMouseEvent must have something to do with how mouseEvent is executed.

Then make

Then, began the New Year, the New Year period to enjoy the life, and did not touch the code and documents (good degenerate feeling……) “, coupled with my job-hopping gap and toss, a little stable after the year, and recently remembered the conjecture half explored before the year, began to continue to do, by the way to collect my heart, good into the state.

First guess — the click event is actually captured in the browser at first, with only the mouseEvent attribute, which is normally part of the console.log(event), and then the browser will produce the click time with HTML and JS. This then triggers the function we bound with JS.

The various properties of the event in general

With this assumption in mind, I started scrolling through mozilla and W3C documentation to learn more about mouseEvent.

MouseEvent is screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, Button, buttons, EventTarget? RelatedTarget.

Buttons are the types of mouse buttons that are left, right, and wheel. So instead of a number, 0 is the left button, 1 is the scroll wheel, 2 is the right button, and everything else is greater than 2.

As you can see from the above, mouseEvent only knows where we are on the screen and what action (the mouse action) we are doing, not what element we are on. This is what FastClick restores to the end of the user click event.

clickEvent.initMouseEvent(this.determineEventType(targetElement), true.true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false.false.false.false, 0, null); // detremineEvenType is a FastClick wrapper that returns mouseEventtypeType, click or mouseDownCopy the code

Initialize a mouse event, and dispatch the mouse event. The browser automatically responds to subsequent operations.

Now look at the definition of click, as shown below:

Click the properties of the

Click should be a topmost Event target. Mozilla’s definition is a bit different with currentTarget and Type.

Mozilla click

Let’s start with the definition of EventTarget: EventTarget is an interface implemented by objects that can receive events and may have listeners for them.

Element, document, and window are the most common event targets, but other objects can be event targets too, for example XMLHttpRequest, AudioNode,AudioContext, and others.

By definition, if it is a Click event, there must be a target to hold the mouse event. In general, target is either element or Document, and if neither is present then it is a Window object. This should make a little bit of sense, but this is the browser’s event mechanism.

event-flow

So that’s what the browser does after initMouseEvent, to find out if there’s a target to respond to that event, and if there’s no target to respond to, then it’s going to go to the window, and normally we don’t do event handling on the Window, we’re going to get no response, and the event is over. If, by chance, a target (normally element) responds, the binding function is executed.

To summarize the process: The user clicks on the screen, and within 300ms, the browser intercepts the action. Instead of actually triggering the click event attached to the relevant element, the browser records the relevant operation data and waits for the next operation. Since we use the zepto library to bind the tap event, the listener touchend is triggered in the event. Execute relevant operation immediately, hide shell layer. At 300ms, the browser thinks the action is Click instead of dbclick, init a mouseEvent at the same screen location, then start the event mechanism, find an element in the same location that is bound to the click handler, execute this function, Over!! This is how penetration occurs. PS: The browser behavior part is guesswork, not verification.

As for solutions: There are many on the web, the best one is FastClick, but fastClick also has other issues, such as swiping and clicking. The other is to use Zepto but preventDefault.

Android’s own Chrome has been fixed and can be used in other ways, official documentation, and Safari currently supports it, but on higher versions, see the Fastclick issue for a discussion



It is well known that there is a 300-millisecond delay in processing click events on mobile devices. It’s this 300-millisecond delay that gives people the experience of being stuck.

The reason for this 300 milliseconds is that in early browser implementations, the browser didn’t know what the user wanted to do when they touched it, so it waited 300 milliseconds to trigger the click event.

Now that we know the cause, how can we solve it?

Option 1- Rough palliative

Since the browser has a 300ms delay in processing click events and TouchStart executes almost immediately, it is estimated that this 300ms delay can be eliminated by changing the listening for all click events to the listening for TouchStart events.

However, this is also a big side effect. The interactive experience of mobile terminals is all about touch, and touchStart will interfere with the processing of other interactive behaviors, such as scrolling and dragging.

Scheme 2- Simulated repair method

Since the browser has this 300ms delay, let’s take the browser’s judgment and manually trigger the Click event, which is also a fastClick solution.

FastClick core code

FastClick.prototype.onTouchEnd = function(event){// Some status monitoring code // From here,if(! This.needsclick (targetElement)) {// If this is not an element that requires native click, shield the native event to prevent double click events.preventdefault (); SendClick (targetElement, event); }}Copy the code

As you can see, FastClick actively fires the click event when the touchEnd condition is met, avoiding the browser’s default 300-millisecond wait. To prevent native click from being triggered, the native click event is also shielded with event.preventDefault().

How does he simulate the Click event

FastClick.prototype.sendClick = function(targetElement, event) {// Here is some status checking logic // Create a mouse event clickEvent = document.createEvent('MouseEvents'); / / initialize the mouse events for the click event clickEvent. InitMouseEvent (enclosing determineEventType (targetElement),true.true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false.false.false.false, 0, null); / / fastclick internal variables, used to identify the click event is native or simulated clickEvent forwardedTouchEvent =true; / / triggers the mouse events on the target element, targetElement. DispatchEvent (clickEvent);Copy the code

We searched the web for fastClick, and most of it said he had solved Zepto’s click-through problem. How? In the last sentence above, the click event he emulated was triggered on the real element fetched by touchEnd, not the element calculated from coordinates.

Finally, the principle is simple, but it is recommended that you use FastClick directly instead of implementing another one yourself. Because, you see his source code inside the notes, there are a lot of special patches, their own implementation of a condensed version will inevitably leak this leak that.





Those of you who have done mobile H5 pages know that the event model of the mobile Web is different from the event model of the PC page. After reading some articles about touch events, I want to review how touch events work, why touch can trigger click events, whether touch events are omnipotent and what problems they may have.

Source of the touch event

Most of the action on a PC web page is mouse-based, that is, in response to mouse events, including mousedown, Mouseup, Mousemove, and Click events. Click action, event trigger process is: mousedown -> mouseup -> click three steps.

The phone doesn’t have a mouse, so touch events are used to do something similar. Touch events include TouchStart, TouchMove, and Touchend. Note that there is no tap event on the phone. The finger triggers the touch event as follows: TouchStart -> TouchMove -> TouchEnd.

The lack of a mouse on the phone does not mean that the phone cannot respond to mouse events. Someone has done a comparative experiment on events on PC and mobile phone to show that mobile phone responds to touch events faster than mouse events.

As you can see on mobile, when we touch the screen, it takes about 300 milliseconds for the Mousedown event to trigger, so the Click event looks like a slow beat on mobile.

The following parameters can be obtained from the touch event

TargetTouches is similar to Touches in that finger information on the same node is filtered out of the changedTouches list of each finger responding to the current event

Where does tap come from

Tap events are familiar to anyone who has used mobile JS libraries such as Zepto or KISSY. We bind click to PC pages and tap to mobile pages. But the native Touch event itself is no TAP, JS library provides tap events are simulated.

As we saw above, there is a 300ms delay in responding to the click event on the phone, so what is the 300ms delay? The browser waits about 300ms after touchEnd to determine whether the user has double tapped. If there is no tap behavior, the click event is emitted, and the click event is not triggered during the double-click process. You can see that the click event trigger represents the end of a round of touch events.

Since tap events are simulated, we can take a look at Zepto’s handling of singleTap events. See line 136-143 of the source code, you can see that singleTap is triggered after the Touchend response has no operation for 250ms.

Click through the scene

With that in mind, we can understand why clickthrough occurs. We often see things like “popovers/floats”. I made a demo.

The whole container has a div for the bottom element and a pop-up div. I added a mask layer to make the pop-up layer look like a modal box.

<div class="container">
    <div id="underLayer"> The underlying element </div> <div id="popupLayer">
        <div class="layer-title"> pop-up layer </div> <div class="layer-action">
            <button class="btn" id="closePopup"</button> </div> </div> <div id="bgMask"></div>
Copy the code

The click event is then bound to the underlying element, while the close button in the pop-up layer is bound to a TAP event.

$('#closePopup').on('tap'.function(e){
    $('#popupLayer').hide();
    $('#bgMask').hide();
});

$('#underLayer').on('click'.function(){
    alert('underLayer clicked');
});
Copy the code

Click the close button, touchEnd triggers the tap first, and the pop-up layer and mask are hidden. After touchend, wait for 300ms to find that there is no other behavior, then continue to trigger click, since the pop-up layer has disappeared, so the current click event target is on the underlying element, so the alert content. The entire event is triggered by TouchEnd -> tap -> click.

And because of the lag (300ms) of the click event, within 300ms the upper element hides or disappears, and a DOM element in the same location below it fires a click event (or a focus event in the case of an input box), making it look as if the target of the click has “penetrated” into the lower layer.

For the full demo, please use the Chrome mobile emulator or scan the QR code on your mobile phone.

Combined with Zepto source code interpretation

Tap in Zepto simulates tap events by listening concurrently to touch events bound to document through event bubbling. The tap event on completion (TouchStart/TouchEnd) needs to bubble to the document before it fires. The click event is triggered when the finger touches and leaves the screen (TouchStart/TouchEnd) before bubbling to the document.

Because the click event has a delay (about 300ms, in order to implement safari’s double click event design), the pop-up layer is hidden immediately after the tap event is executed, while the click event is still 300ms late. When 300ms arrives, click actually hits the element below the hidden element.

Emitted if the element directly below is bound with a Click event, or not if there is no bound click event. If the input box is directly below (or select/radio/checkbox), click the default focus to pop up the input keyboard, and the above “dot through” phenomenon will appear.

Penetrating solutions

1. Keep out

Due to the lag of the click event, the element that was clicked disappears during this time, thus “penetrating.” So we follow this idea, we can make an element fade, like fadeOut in jQuery, and set the animation duration to be greater than 300ms, so that when delayed click is triggered, it does not “penetrate” into the elements below.

Similarly, without time-lapse animation, we can dynamically generate a transparent element at the touch position, so that when the upper element disappears and the delayed click comes, it hits the transparent element and doesn’t “penetrate” to the bottom. Remove the generated transparent element after a certain timeout. See demo

2. pointer-events

Pointer – Events is an attribute in CSS3. It has a number of values. The most useful ones are Auto and None.

The auto effect is the same as if the pointer-events attribute is not defined. The mouse does not penetrate the current layer. The None element is no longer the target of mouse events, and the mouse does not listen for the current layer but for elements in the layer below. But if its child element has pointer-events set to some other value, such as auto, the mouse will still listen for that child element.

See the code for an experiment on bubbling events after using pointer-events

Therefore, the solution to “penetration” is simple, as shown in the following demo

$('#closePopup').on('tap'.function(e){
    $('#popupLayer').hide();
    $('#bgMask').hide();

    $('#underLayer').css('pointer-events'.'none');

    setTimeout(function(){
        $('#underLayer').css('pointer-events'.'auto');
    }, 400);
});
Copy the code

3. fastclick

Using the FastClick library, the idea is to cancel the click event (see lines 164-173 of the source code) and use touchend to simulate a quick click (see lines 521-610 of the source code).

FastClick.attach(document.body);
Copy the code

Click is used for all click events from now on, with no “penetration” issues and no 300ms latency. Resolve a penetrating demo

Someone (Ye Xiaochai) has done a detailed analysis of the event mechanism, good at giving guidance, and analyzed the fastclick source code to simulate the creation of their own events. Take a look at this article and be sure to have a deeper understanding of events on mobile