解決iOS瀏覽器touch+click事件延時閃爍的問題

閃爍問題

由于在iOS Safari上click事件存在300ms響應(yīng)延時讳嘱,所以為touch事件添加樣式,會和click事件默認樣式疊加而產(chǎn)生閃爍問題复凳。

因為ios safari瀏覽器中對觸摸事件的響應(yīng)順序是:

ontouchstart -> ontouchmove -> ontouchend -> (300ms) onclick

快速解決問題的方法:

消除click默認樣式

通過講click事件樣式的alpha通道設(shè)置為0(透明)辰晕,消除click事件的閃爍效果

-webkit-tap-highlight-color: rgba(0,0,0,0);

此時顽分,瀏覽器默認的click事件樣式就看不到了,但是延時仍然存在丝里。

消除click延時

FastClick 是FT Labs專門為解決移動端瀏覽器 300 毫秒點擊延遲問題所開發(fā)的一個輕量級的庫曲初。簡而言之,F(xiàn)astClick 在檢測到 touchend 事件的時候杯聚,會通過 DOM 自定義事件立即觸發(fā)一個模擬 click 事件臼婆,并把瀏覽器在 300 毫秒之后真正觸發(fā)的click事件阻止掉。

FastClick 非常實際地解決 300 毫秒點擊延遲的問題幌绍,唯一的缺點可能也就是該腳本的文件尺寸 (盡管它只有10kb)颁褂。如果你連這10kb都接受不了的話,那么移動端類庫 jQuery和zepto.js都支持tap事件來解決這個問題傀广,盡管它們的響應(yīng)速度比FastClick慢一些颁独。

延伸閱讀

iOS端click事件響應(yīng)延時(300ms)的由來

這要追溯至 2007 年初。蘋果公司在發(fā)布首款 iPhone 前夕伪冰,遇到一個問題:當(dāng)時的網(wǎng)站都是為大屏幕設(shè)備所設(shè)計的奖唯。于是蘋果的工程師們做了一些約定,應(yīng)對 iPhone 這種小屏幕瀏覽桌面端站點的問題糜值。 這當(dāng)中最出名的丰捷,當(dāng)屬雙擊縮放(double tap to zoom),這也是會有上述 300 毫秒延遲的主要原因寂汇。雙擊縮放病往,顧名思義,即用手指在屏幕上快速點擊兩次骄瓣,iOS 自帶的 Safari 瀏覽器會將網(wǎng)頁縮放至原始比例停巷。

那么這和 300 毫秒延遲有什么聯(lián)系呢? 假定這么一個場景榕栏。用戶在 iOS Safari 里邊點擊了一個鏈接畔勤。由于用戶可以進行雙擊縮放或者雙擊滾動的操作,當(dāng)用戶一次點擊屏幕之后扒磁,瀏覽器并不能立刻判斷用戶是確實要打開這個鏈接庆揪,還是想要進行雙擊操作。因此妨托,iOS Safari 就等待 300 毫秒缸榛,以判斷用戶是否再次點擊了屏幕吝羞。 鑒于iPhone的成功,其他移動瀏覽器都復(fù)制了 iPhone Safari 瀏覽器的多數(shù)約定内颗,包括雙擊縮放钧排,幾乎現(xiàn)在所有的移動端瀏覽器都有這個功能。

之前人們剛剛接觸移動端的頁面均澳,在欣喜的時候往往不會care這個300ms的延時問題恨溜,可是如今touch端界面如雨后春筍,用戶對體驗的要求也更高找前,這300ms帶來的卡頓慢慢變得讓人難以接受糟袁。

FastClick解決延遲點擊的源碼解析。

首先纸厉,我們來看FastClick的使用系吭。

window.addEventListener( "load", function() {
   FastClick.attach( document.body );
 }, false );

這樣就解決了 300 毫秒點擊延遲的問題。

FastClick的源碼:

FastClick.attach = function(layer) {
    ‘use strict‘;
    return new FastClick(layer);
};

在FastClick的構(gòu)造函數(shù)中颗品,會有下面這段代碼:

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.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(‘touchend‘, this.onTouchEnd, false);
layer.addEventListener(‘touchcancel‘, this.onTouchCancel, false);

也就是在document.body上綁定了click肯尺,touchstart,touchend躯枢,touchcancel事件则吟。

這里假設(shè),我們的頁面有一個button锄蹂,綁定了click事件氓仲。

當(dāng)用戶點擊此button時,會先觸發(fā)touchstart事件得糜,這時敬扛,會冒泡到document.body中,于是就會執(zhí)行:

FastClick.prototype.onTouchStart = function(event) {
    ‘use strict‘;
    var targetElement, touch, selection;

    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;
};

這個回調(diào)函數(shù)主要做了以下事情:

獲取我們當(dāng)前觸發(fā)touchstart的元素朝抖,這里是button啥箭。

然后將鼠標的信息記錄了下來,記錄鼠標的信息主要是為了在后面touchend觸發(fā)時治宣,根據(jù)這里得到的x急侥、y判斷是否為click。

之后侮邀,會觸發(fā)touchend事件坏怪,然后冒泡到document.body上,執(zhí)行以下代碼:

FastClick.prototype.onTouchEnd = function(event) {
    ‘use strict‘;
    var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
    if (this.touchHasMoved(event) || (event.timeStamp - this.trackingClickStart) > 300) {
        this.trackingClick = false;
        this.targetElement = null;
    }
    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);
    }
    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;
};

注意上面的代碼中绊茧,event.preventDefault();會阻止真實的click事件的觸發(fā)铝宵,因此,在button上面的click事件不會觸發(fā)按傅。

接下來捉超,我們只需要查看sendClick方法胧卤。

FastClick.prototype.sendClick = function(targetElement, event) {
    ‘use strict‘;
    var clickEvent, touch;
    if (document.activeElement && document.activeElement !== targetElement) {
        document.activeElement.blur();
    }
    touch = event.changedTouches[0];

    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);
};

在此方法中唯绍,會創(chuàng)建一個自定義的click事件拼岳,然后在button上立即觸發(fā),于是况芒,button綁定的click的事件回調(diào)函數(shù)馬上執(zhí)行惜纸,因此就沒有300ms延遲了。

上面的initMouseEvent方法的前三個參數(shù)的意思:1.事件類型绝骚,2.是否冒泡耐版,3.是否阻止瀏覽器的默認行為。

自定義的click事件阻止了瀏覽器的默認行為压汪,事件冒泡粪牲,于是執(zhí)行document.body的click事件回調(diào)函數(shù)。代碼如下:

FastClick.prototype.onClick = function(event) {
    ‘use strict‘;
    var permitted;
    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;
};

然后里面有一句 permitted = this.onMouse(event);于是止剖,我們查看onMouse方法:

FastClick.prototype.onMouse = function(event) {
    ‘use strict‘;
    if (!this.targetElement) {
        return true;
    }
    if (event.forwardedTouchEvent) {
        return true;
    }
    if (!event.cancelable) {
        return true;
    }
    if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
        if (event.stopImmediatePropagation) {
            event.stopImmediatePropagation();
        } else {
            event.propagationStopped = true;
        }
        event.stopPropagation();
        event.preventDefault();
        return false;
    }
    return true;
};

此方法腺阳,會阻止模擬的click事件的冒泡以及默認行為。

到此穿香,解決了移動端瀏覽器click事件延遲300ms的問題亭引。

<i>本文大部分內(nèi)容來自互聯(lián)網(wǎng)</i>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市皮获,隨后出現(xiàn)的幾起案子焙蚓,更是在濱河造成了極大的恐慌,老刑警劉巖洒宝,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件购公,死亡現(xiàn)場離奇詭異,居然都是意外死亡雁歌,警方通過查閱死者的電腦和手機宏浩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來将宪,“玉大人绘闷,你說我怎么就攤上這事〗咸常” “怎么了印蔗?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丑勤。 經(jīng)常有香客問我华嘹,道長,這世上最難降的妖魔是什么法竞? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任耙厚,我火速辦了婚禮强挫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薛躬。我一直安慰自己俯渤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布型宝。 她就那樣靜靜地躺著八匠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趴酣。 梳的紋絲不亂的頭發(fā)上梨树,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音岖寞,去河邊找鬼抡四。 笑死,一個胖子當(dāng)著我的面吹牛仗谆,可吹牛的內(nèi)容都是我干的指巡。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼胸私,長吁一口氣:“原來是場噩夢啊……” “哼厌处!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岁疼,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤阔涉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后捷绒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑰排,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年暖侨,在試婚紗的時候發(fā)現(xiàn)自己被綠了椭住。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡字逗,死狀恐怖京郑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情葫掉,我是刑警寧澤些举,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站俭厚,受9級特大地震影響户魏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一叼丑、第九天 我趴在偏房一處隱蔽的房頂上張望关翎。 院中可真熱鬧,春花似錦鸠信、人聲如沸纵寝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽店雅。三九已至政基,卻和暖如春贞铣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沮明。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工辕坝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荐健。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓酱畅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親江场。 傳聞我的和親對象是個殘疾皇子纺酸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容