點透 & 解決方案

點透 & 解決方案前端

學習map:node

  • 現象:再現現象,總結致使點透出現的狀況
  • 分析緣由
  • 解決辦法

現象

再現點透現象請使用一下方式:android

  1. 手機訪問傳送門
  2. 複製連接到連圖生成二維碼後掃一掃
  3. 或者打開chrome手機視圖並打開touch screen

zepto-隱藏元素-點透

代碼:ios

zepto-最新-沒有點透

代碼:git

原生js-隱藏元素-點透

代碼:github

原生js-元素出現-點透

代碼:chrome

綜上,致使點透現象出現的場景:瀏覽器

  1. 元素z軸重疊;
  2. 綁定了touch事件的元素消失或移走;
  3. 綁定有類click事件的元素出如今點擊區域;

PS:
a連接的href跳轉、input、select等表單元素的聚焦並彈起軟鍵盤,等觸發的事件和click同樣,因爲歷史緣由在手機端也會表現出300ms延遲,所以也均可以看作是click事件,我叫他類click事件,demo可用爪機狠戳事件觸發順序&click延遲demo或者複製連接到連圖生成二維碼後掃一掃。類click事件,也是一種瀏覽器默認行爲,可被event.preventDefault()阻止。iphone

代碼:函數

分析

事件觸發順序&click延遲demo這個demo能夠看出:

  • iphone系手機:mouse事件在touchend以後纔開始,mouse事件覆蓋了click事件,屏蔽了input彈出鍵盤的默認行爲
  • 安卓手機:mouse事件會先於touch事件開始,而遲於其結束,mouse事件沒有覆蓋input的聚焦彈軟鍵盤的瀏覽器默認行爲
  • 在ios設備上的瀏覽器[UC瀏覽器除外]能明顯感覺到click延遲
  • 不管是android仍是ios仍是PC的touch screen的click(or mouse)事件都是遲於touchend事件被觸發的
  • 所以,從手指觸摸屏幕到離開屏幕,前後觸發了touchstarttouchendclick

因爲咱們在touchend階段z軸層級已經發生了變化,當click被觸發時候,可以被點擊的元素則是當前z軸離用戶最近的層,根據click事件的觸發規則:

在被觸發時,當前有綁定click事件的元素顯示,且在面朝用戶的最前端時,才觸發click事件

所以touchend以後符合條件的綁定了click事件的元素被點透。

總而言之:出現點透是因爲移動端click事件遲於touch事件被觸發致使的。

解決辦法

不優雅的辦法:

1. 對於默認綁定了類click事件的元素(如a、input、select等)
  • touchend + preventDefault及時取消touch元素的默認click事件,即if(eve == "touchend") e.preventDefault();

  • 若是犧牲點性能無所謂的話,能夠將可能在z軸方向上引發點透現象的元素綁定成click事件,好比遮罩層之類的,不過還能夠增長些許有趣的交互抵消用戶的焦躁心理,好比:demo-ripple

2. 對於沒有默認綁定類click事件的元素

統一使用touch事件。z軸上都綁定touchstarttouchendtap不用阻止默認行爲也不會穿透。

使用上述方法有很明顯的缺點和不方便:

使用touchend + preventDefault要在同一個元素上綁定2個事件,zepto能夠封裝成tap事件,咱們也能夠,自定義tap事件阻止點透

代碼:

在本demo中,雖然沒有出現點透現象,可是點擊出現彈層之後你會發現點擊a連接、span、input都沒有任何反映了,這是由於在touchend裏阻止瀏覽器默認行爲,觸發自定義tap事件,不只會阻止掉了input的軟鍵盤彈出,還會阻止一切非tap事件,解決辦法就是使用合成的click事件去覆蓋會延遲的click事件。

重寫click事件

代碼:

event.initEvent('click', bubbles, true);

touch_target.addEventListener("click", handle, false);

這樣再次點擊,彈層上的a連接和span的click事件都響應地很迅速,然而input和select的彈出軟鍵盤的功能被閹割了,實際上input彈出軟鍵盤的事件是focus()事件

3. fastclick

讀fastclick源碼

layer.removeEventListener('touchend', this.onTouchEnd, false);

FastClick.prototype.onTouchEnd = function(event) {
    ...
    targetTagName = targetElement.tagName.toLowerCase();
    if (targetTagName === 'label') {
        forElement = this.findControl(targetElement);
        if (forElement) {
            this.focus(targetElement);
            if (deviceIsAndroid) {
                return false;
            }

            targetElement = forElement;
        }
    } else if (this.needsFocus(targetElement)) {

        if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
            this.targetElement = null;
            return false;
        }

        this.focus(targetElement);
        this.sendClick(targetElement, event);

        if (!deviceIsIOS || targetTagName !== 'select') {
            this.targetElement = null;
            event.preventDefault();
        }

        return false;
    }
    
// needsFocus
FastClick.prototype.needsFocus = function(target) {
    switch (target.nodeName.toLowerCase()) {
    case 'textarea':
        return true;
    case 'select':
        return !deviceIsAndroid;
    case 'input':
        switch (target.type) {
        case 'button':
        case 'checkbox':
        case 'file':
        case 'image':
        case 'radio':
        case 'submit':
            return false;
        }

        // No point in attempting to focus disabled inputs
        return !target.disabled && !target.readOnly;
    default:
        return (/\bneedsfocus\b/).test(target.className);
    }
};
// sendClick 
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);
};

上面代碼的意思就是:在目標元素上綁定touchend事件,在事件處理函數裏,若是是須要focus的表單元素被點擊,則先觸發他們的focus事件,再觸發自定義的click事件,fastclick之因此大,就是由於對不少表單元素在各個系統的各個版本的不一樣表現作了兼容,不只解決了click以及類click的延遲問題,並且當檢測到當前頁面使用了基於 <meta> 標籤或者 touch-action 屬性的解決方案時,會靜默退出。能夠說,這是真正的跨平臺方案出來以前一種很好的變通方案。而zepto 只是爲普通的點擊事件封裝了一個更快的tap事件,類click事件的延遲問題並無獲得解決,並且移動端使用的tap事件,若是沒作設備判斷兼容PC的話,PC端的點擊事件將得不到響應,這會很影響網站的可用性和可訪問性。不過zepto封裝了一系列移動端很須要的功能,好比swipeLeft、swipeRight、swipeUp、等等,兩者各有春秋,兼併二者優點的庫我目前沒遇到,不過能夠嘗試本身寫一個,加個todo吧。

4. tap.js

tap.js源碼只有不到200行,大體看了下,並不能解決類click的延遲問題,雞肋!

相關文章
相關標籤/搜索