I spent a few days last week writing a responsive website, while writing about mobile interaction. There is a problem that when you click on the options in the drop-down box, the elements behind the drop-down box are also clicked. In fact, this is the famous clickthrough phenomenon, so I took the time to comb through this problem over the weekend. Then, after referring to some articles, I sorted out this summary, which is also a record of this problem. All references are at the bottom of the article. If you’re interested, you can check it out.

300 ms delay

Cause of delay

The 300ms delay was an operation proposed by Apple engineers to optimize the interactive experience before the release of the first iPhone in early 2007. Websites were written for large screen devices like PCS, but now you need to browse desktop websites on small screens. When the user zooms in with his finger, there is a double tap to Zoom interaction. A quick double click in Safari, the browser that comes with iOS, zooms the page back to its original scale. So in this browser, the user will have the required behavior of clicking or double-clicking. The browser can’t immediately tell if the user wants to click or double-click. So Safari waited 300ms to see what the user wanted to do. Since Apple’s success, other browsers have copied this behavior, and 300ms has become a convention for most browsers. At the beginning of the rise of mobile terminals,300ms was acceptable. However, as users have higher and higher requirements for interactive experience,300ms has become an intolerable point for users.

Solve the latency

The reason for the 300ms delay is briefly described above. So how do you solve the 300ms delay for click events in mobile development? At present, there are many online solutions. I have also tested those solutions and sorted them out as follows:

Disable zoom

Disabling browser zooming means adding a meta header to our page to indicate that the page is not zooming. Download several browsers in the App Store to try, quark browser and QQ browser test is effective. Safari,Chrome,Firefox. This test result is different from what I read in some articles, why is this?

<meta name="viewport" content="Initial - scale = 1.0, the maximum - scale = 1.0, user - scalable = no" />
Copy the code

Change viewport width

Changed the default viewport width so that all browsers have no latency except safari, which has 300ms. It seems that this solution has not been approved by Safari yet. Because we have adapted the page size for the user and prevented the user from zooming, the browser no longer has to determine if the user double-clicks to zoom and automatically cancels the 300ms delay for the click event

<meta name="viewport" content="width=device-width" /> 
Copy the code

touch-action

Set the touch-action property, which disables any default behavior of the browser agent on the element, including zooming, moving, dragging, and so on. It disables all touch-type interaction events and prevents the page from scrolling. It feels like in a slightly more complicated real development, it should not be set up like this.

html {
  touch-action: none;
}
Copy the code

Reference fastclick library

1fastclick principle: when the touchend event is detected, the DOM custom event immediately simulates a click event and blocks the browser’s original click event 300ms later. The downside is that the script is relatively large and can sometimes be buggy

Since click events are so much shit, can touch events be used instead of click events? The answer is no, if we use the TouchStart event instead, which is triggered when the user touches the screen with their finger, but sometimes they don’t want to click, they just want to swipe. On the other hand, ghost click can occur in some scenarios.

Click through

What is clickthrough

Page elements A and B, with B above A. The touchStart event is registered on top of B, and the B element is hidden in the callback function. When we click on the B element, in addition to B being hidden,A’s click event is also triggered. This is because in mobile browsers, events are executed in the order touchStart => TouchMove => TouchEnd => click. Click has a 300ms delay. When the touchStart event of B is triggered,B is hidden. After 300ms, the browser triggers the click event, which has been sent to element A.

Event execution sequence

The event on the mobile side is a touch event, also called a touch event because it is touched with a finger. Of course, there are click events. As we mentioned above, in mobile browsers, events are executed in the order touchStart => TouchMove => TouchEnd => click. So let’s talk about what this is.

  • Touchstart is triggered when a finger is placed on the screen
  • Touchmove is triggered when a finger slides across the screen
  • Touchend is triggered when your finger leaves the screen

The click event is executed at the end. In general, click on the finger to the screen, and did not move, and then leave, and this began to touch the left of the screen time short to trigger finger, if fingers moved, will not trigger the click event (see some places said some browsers will be allowed to have a small mobile value, the specific situation is not very clear). So the correct firing sequence is one of two:

  • touchstart => touchmove => touchend
  • touchstart => touchend => click

Touchmove, it might not fire, it might fire a bunch of times, but if it fires Touchmove, then click won’t fire. To verify this, run the following code in Chrome’s mobile mode.

<<button id="btn">click me</button>
<div id="app"></div>
Copy the code
let btn = document.querySelector('#btn')
let app = document.querySelector('#app')
let s = ' '

btn.addEventListener('click'.function(){
  s += 'click '
  app.innerText = s
})

btn.addEventListener('touchstart'.function(e){
  s += 'touchstart '
  app.innerText = s
  // e.preventDefault()
})

btn.addEventListener('touchmove'.function(){
  s += 'touchmove '
  app.innerText = s
})

btn.addEventListener('touchend'.function(){
  s += 'touchend '
  app.innerText = s
})
Copy the code

If we just click on the button, it will print out touchStart, Touchend Click. I’m gonna quickly swipe left to right on the button, and it’s gonna print out touchStart, Touchmove, touchmove, touchmove, touchend, the number of touchmoves varies.

Touch events, like click, have event objects. Touchstart and TouchMove use event. Touched to obtain information about a finger, such as the position of the touched point. Touchend, however, cannot be obtained via Event. touched. Because the finger is already gone. But you can view finger touches via Event.ChangedTouches.

Solve click penetration

There are several solutions to click through, and you can choose the corresponding solution according to the actual situation of your project.

Element block

Added an invisible element to prevent event penetration. Create a new transparent element at the location where the event was triggered. Then, when the click event comes 300ms later, the transparent element will be clicked and deleted. The downside is that it’s hard to write, and sometimes when the user clicks quickly, the event for the element below may not fire because the transparent element has not been cleared by the timer. Of course, there is a solution very similar to this, that is to make a hidden animation, more than 300ms, after the click event after the delay, the upper element has not disappeared, so that the lower element will not be clicked.

<div class="div1"></div>
<div class="div2"></div>
Copy the code
.div1 {
  width: 200px;
  height: 200px;
  background-color: pink;
}
.div2 {
  width: 200px;
  height: 200px;
  background-color: orange;
  position: relative;
  top: -100px;
  display: block;
  opacity: 1;
}
Copy the code
let div1 = document.querySelector('.div1')
let div2 = document.querySelector('.div2')

div1.addEventListener('click'.function(){
  console.log('div1')
})

div2.addEventListener('touchstart'.function(e){
  console.log('div2')
  let el = document.createElement('div')
  el.style.width = '200px'
  el.style.height = '200px'
  el.style.opacity = '0'
  el.style.position = 'relative'
  el.style.top = '-100px'
  document.body.appendChild(el)
  this.style.display = 'none'
  setTimeout((a)= > {
    document.body.removeChild(el)
  }, 400)})Copy the code

Blocking default events

Using event.preventDefault(), change the above code to

div2.addEventListener('touchstart'.function(){
  console.log('div2')
  this.style.display = 'none'
  e.preventDefault()
})
Copy the code

pointer-events

Use pointer-Events, which is a cSS3 property. Most of the attributes we use are auto and None. The other attributes are basically for SVG.

The initial value is auto, which applies to all elements and behaves as if the pointer-events attribute was not specified. The mouse does not penetrate the current layer. None is not the target of mouse events. Instead of listening for the current layer, the mouse listens for elements in the layer below. However, if its child element has pointer-events set to some other value, the mouse will still listen for that child element. Run the following code and observe the effect.

.div1 {
  width: 200px;
  height: 200px;
  background-color: orange;
}
.div2 {
  width: 100px;
  height: 100px;
  background-color: pink;
  pointer-events: none;
}
.div3 {
  width: 50px;
  height: 50px;
  background-color: #00BFFF;
  pointer-events: auto;
}
Copy the code
<div class="div1">
    div1
    <div class="div2">
        div2
        <div class="div3">
            div3
        </div>
    </div>
</div>
Copy the code
let div1 = document.querySelector('.div1')
let div2 = document.querySelector('.div2')
let div3 = document.querySelector('.div3')

div1.addEventListener('click'.function(){
    console.log('div1')
})
div2.addEventListener('click'.function(){
    console.log('div2')
})
div3.addEventListener('click'.function(){
    console.log('div3')})Copy the code

Therefore, the solution to click penetration is to set pointer-events of the lower element to None, and then set a timer to change the attribute value of the lower element to Auto after a certain period of time.

Fastclick library

Introduce the FastClick library.

<script src="https://lib.baomitu.com/fastclick/1.0.6/fastclick.min.js"></script>
Copy the code

Here is how it is written in native JS, which varies from library to library (such as jQuery) or framework (such as Vue). Nevertheless basic it is much the same, search a lot on the net, when everybody uses, go searching next went.

if('addEventListener' in document) {document.addEventListener('DOMContentLoaded'.function(){
    FastClick.attach(document.body)
  }, false)}Copy the code

conclusion

Since we are having problems with 300ms and click penetration this time, let’s just research and record what everyone’s solution is. The next time you face a similar problem, you’ll probably know what to do. Here’s a list of links to some of the articles I referenced:

  • Blog.csdn.net/fhjdzkp/art…
  • Blog.csdn.net/lululove198…
  • www.jianshu.com/p/6e2b68a93…
  • Developer.mozilla.org/zh-CN/docs/…
  • Blog.csdn.net/zhuyinqinyi…
  • zhuanlan.zhihu.com/p/87579573
  • Blog.csdn.net/thunderevil…