一、前言
移動端瀏覽器提供一個特殊的功能:雙擊(double tap)縮放表谊。
二、移動端延遲300ms的原因
為什么要用觸摸事件盖喷?觸摸事件是移動端瀏覽器特有的html5事件爆办。
因為移動端的click有很大延遲(大約300ms),300ms延遲來自判斷雙擊和長按课梳,因為只有默認(rèn)等待時間結(jié)束以確定沒有后續(xù)動作發(fā)生時距辆,才會觸發(fā)click事件。而觸摸事件的延遲則是非常短的暮刃,使用觸摸事件的能夠提高頁面響應(yīng)速度跨算,帶來更好的用戶體驗。
重點:由于移動端會有雙擊縮放的這個操作沾歪,因此瀏覽器在click之后要等待300ms漂彤,****看用戶有沒有下一次點擊,也就是這次操作是不是雙擊灾搏。
三挫望、瀏覽器開發(fā)商的解決方案
1、方案一:禁用縮放
當(dāng)HTML文檔頭部包含如下meta
標(biāo)簽時:
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
表明這個頁面是不可縮放的狂窑,那雙擊縮放的功能就沒有意義了媳板,此時瀏覽器可以禁用默認(rèn)的雙擊縮放行為并且去掉300ms的點擊延遲。
缺點:就是必須通過完全禁用縮放來達(dá)到去掉點擊延遲的目的泉哈,然而完全禁用縮放并不是我們的初衷蛉幸,我們只是想禁掉默認(rèn)的雙擊縮放行為,這樣就不用等待300ms來判斷當(dāng)前操作是否是雙擊丛晦。但是通常情況下奕纫,我們還是希望頁面能通過雙指縮放來進(jìn)行縮放操作,比如放大一張圖片烫沙,放大一段很小的文字匹层。
2、方案二:更改默認(rèn)的視口窗口
為了讓桌面站點能在移動端瀏覽器正常顯示锌蓄,移動端瀏覽器默認(rèn)的視口寬度升筏!=設(shè)備瀏覽器視窗寬度,而是視口寬度要比設(shè)備寬度大瘸爽,通常是980px您访。
我們可以通過以下標(biāo)簽來設(shè)置視口寬度為設(shè)備寬度。
<meta name="viewport" content="width=device-width">
對移動端坐過適配和優(yōu)化了剪决,這個時候就不需要雙擊縮放了灵汪。如果能夠識別出一個網(wǎng)站是響應(yīng)式的網(wǎng)站檀训,那么移動端瀏覽器就可以自動禁掉默認(rèn)的雙擊縮放行為并且去掉300ms的點擊延遲。如果設(shè)置了上述meta
標(biāo)簽识虚,那瀏覽器就可以認(rèn)為該網(wǎng)站已經(jīng)對移動端做過了適配和優(yōu)化肢扯,就無需雙擊縮放操作了妒茬。
這個方案相比方案一的好處在于担锤,它沒有完全禁用縮放,而只是禁用了瀏覽器默認(rèn)的雙擊縮放行為乍钻,但用戶仍然可以通過雙指縮放操作來縮放頁面肛循。
方案三:css 的 touch-action
除了IE之外的大部分瀏覽器都不支持這個新的CSS屬性。touch-action這個CSS屬性银择。這個屬性指定了相應(yīng)元素上能夠觸發(fā)的用戶代理(也就是瀏覽器)的默認(rèn)行為多糠。如果將該屬性值設(shè)置為touch-action: none,那么表示在該元素上的操作不會觸發(fā)用戶代理的任何默認(rèn)行為浩考,就無需進(jìn)行300ms的延遲判斷夹孔。
四、代碼解決方案
1析孽、方案一:指針事件polyfill
除了IE搭伤,其他大部分瀏覽器都還不支持指針事件。有一些JS庫袜瞬,可以讓我們提前使用指針事件怜俐。比如:
(1)谷歌的Polymer
(2)微軟的HandJS
(3)@Rich-Harris 的 Points
關(guān)心的不是指針事件,而是與300ms延遲相關(guān)的CSS屬性touch-action邓尤。由于除了IE之外的大部分瀏覽器都不支持這個新的CSS屬性拍鲤,所以這些**指針事件的polyfill必須通過某種方式去模擬支持這個屬性**。一種方案是JS去請求解析所有的樣式表汞扎,另一種方案是將
touch-action作為html標(biāo)簽的屬性季稳。
2、方案二:****FastClick
FastClick是FT Labs專門為解決移動端瀏覽器 300 毫秒點擊延遲問題所開發(fā)的一個輕量級的庫澈魄。FastClick的實現(xiàn)原理是在檢測到touchend事件的時候景鼠,會通過DOM自定義事件立即出發(fā)模擬一個click事件,并把瀏覽器在300ms之后的click事件阻止掉一忱。
五莲蜘、點擊穿透問題
說完移動端點擊300ms延遲的問題,還不得不提一下移動端點擊穿透的問題帘营。既然click點擊有300ms的延遲票渠,那對于觸摸屏,我們直接監(jiān)聽touchstart事件不就好了嗎芬迄?
使用touchstart去代替click事件有兩個不好的地方问顷。
第一:touchstart是手指觸摸屏幕就觸發(fā),有時候用戶只是想滑動屏幕,卻觸發(fā)了touchstart事件杜窄,這不是我們想要的結(jié)果肠骆;
第二:使用touchstart事件在某些場景下可能會出現(xiàn)點擊穿透的現(xiàn)象。
1塞耕、什么是點擊穿透蚀腿?
假如頁面上有兩個元素A和B。B元素在A元素之上扫外。我們在B元素的touchstart事件上注冊了一個回調(diào)函數(shù)莉钙,該回調(diào)函數(shù)的作用是隱藏B元素。我們發(fā)現(xiàn)筛谚,當(dāng)我們點擊B元素磁玉,B元素被隱藏了,隨后驾讲,A元素觸發(fā)了click事件倔约。
這是因為在移動端瀏覽器爽醋,事件執(zhí)行的順序是touchstart > touchend > click。而click事件有300ms的延遲,當(dāng)touchstart事件把B元素隱藏之后樱哼,隔了300ms凌简,瀏覽器觸發(fā)了click事件依沮,但是此時B元素不見了盗舰,所以該事件被派發(fā)到了A元素身上。如果A元素是一個鏈接扎谎,那此時頁面就會意外地跳轉(zhuǎn)碳想。
2、點擊穿透現(xiàn)象3種情況
(1)點擊穿透問題:點擊蒙層(mask)上的關(guān)閉按鈕毁靶,蒙層消失后發(fā)現(xiàn)觸發(fā)了按鈕下面元素的click事件胧奔。
(2)跨頁面點擊穿透問題:如果按鈕下面恰好是一個有href屬性的a標(biāo)簽,那么頁面就會發(fā)生跳轉(zhuǎn)因為 a標(biāo)簽跳轉(zhuǎn)默認(rèn)是click事件觸發(fā) 预吆,所以原理和上面的完全相同
(3)點擊穿透問題:這次沒有mask了龙填,直接點擊頁內(nèi)按鈕跳轉(zhuǎn)至新頁,然后發(fā)現(xiàn)新頁面中對應(yīng)位置元素的click事件被觸發(fā)了拐叉。
3岩遗、解決方案
2種思路:
(1)不要混用touch和click。既然touch之后300ms會觸發(fā)click凤瘦,只用touch或者只用click就自然不會存在問題了宿礁。
(2)用掉(或者說是消費掉)touch之后的click。依舊用tap蔬芥,只是在可能發(fā)生點擊穿透的情形做額外的處理梆靖,拿個東西來擋住控汉、或者tap后延遲350毫秒再隱藏mask、pointer-events返吻、在下面元素的事件處理器里做檢測(配合全局flag)
詳細(xì)方案:
(1)只用touch
最簡單的解決方案姑子,完美解決點擊穿透問題。
把頁面內(nèi)所有click全部換成touch事件 touchstart
测僵、’touchend’街佑、’tap’, 需要特別注意 a標(biāo)簽恨课,a標(biāo)簽的href也是click舆乔,需要去掉換成js控制的跳轉(zhuǎn)岳服,或者直接改成span + tap控制跳轉(zhuǎn)剂公。
(2)只用click
下下策 ,因為會帶來300ms延遲吊宋,頁面內(nèi)任何一個自定義交互都將增加300毫秒延遲纲辽,想想都慢。不用touch就不會存在touch之后300ms觸發(fā)click的問題璃搜。
(3)拿個東西擋住
比較笨的方法拖吼, 千萬不要用。更多信息請查看 【移動端兼容問題研究】javascript事件機(jī)制詳解(涉及移動兼容)
(4)tap后延遲350ms再隱藏mask
改動最小这吻,缺點是隱藏mask變慢了吊档,350ms還是能感覺到慢的。
(5)pointer-events
比較麻煩且有缺陷唾糯, 不建議使用怠硼。mask隱藏后,給按鈕下面元素添上 pointer-events: none; 樣式移怯,讓click穿過去香璃,350ms后去掉這個樣式,恢復(fù)響應(yīng)舟误。缺陷是mask消失后的的350ms內(nèi)葡秒,用戶可以看到按鈕下面的元素點著沒反應(yīng),如果用戶手速很快的話一定會發(fā)現(xiàn)嵌溢。
(6)在下面元素的事件處理器里做檢測(配合全局flag)
比較麻煩眯牧, 不建議使用。全局flag記錄按鈕點擊的位置(坐標(biāo)點)赖草,在下面元素的事件處理器里判斷event的坐標(biāo)點学少,如果相同則是那個可惡的click,拒絕響應(yīng)疚顷。
(7)fastclick
好用的解決方案旱易,不介意多加載幾KB的話禁偎, 不建議使用 ,因為有人遇到了bug阀坏,更多信息請查看: Fastclick 導(dǎo)致click事件觸發(fā)兩次的問題如暖。
首先引入fastclick庫,再把頁面內(nèi)所有touch事件都換成click忌堂,其實稍微有點麻煩盒至,建議引入這幾KB就為了解決點透問題不值得,不如用第一種方法呢士修。
六枷遂、瀏覽器事件觸發(fā)的順序
touchstart --> mouseover(有的瀏覽器沒有實現(xiàn)) --> mousemove(一次) -->mousedown --> mouseup --> click -->touchend
Touch 事件中,常用的為 touchstart, touchmove, touchend 三種棋嘲。除此之外還有touchcancel酒唉。 注意,原生事件中并沒有tap事件沸移。
事件描述如下:
事件 | 描述 | 觸發(fā)時機(jī) |
---|---|---|
touchstart | 開始觸摸 | 手指接觸屏幕時立即觸發(fā) |
touchmove | 移動或拖拽 | 取決于系統(tǒng)和瀏覽器 |
touchend | 觸摸結(jié)束 | 手指離開屏幕時立即出發(fā) |
而Touch事件的觸發(fā)一般通過手指痪伦,還會存在多點觸控,拖拽方向等情況雹锣。列出幾個重要參數(shù)如下:
參數(shù) | 含義 |
---|---|
touches | 屏幕中每根手指信息列表 |
targetTouches | 和touches類似网沾,把同一節(jié)點的手指信息過濾掉 |
changedTouches | 響應(yīng)當(dāng)前事件的每根手指的信息列表 |
代碼獲取如下:
elemenrRef.addEventListener('touchstart', function(e) {
console.log(e.touches, e.targetTouches, e.changedTouches);}
);
手指觸發(fā)觸摸事件的過程如下:
touchstart --> mouseover(有的瀏覽器沒有實現(xiàn)) --> mousemove(一次) -->mousedown --> mouseup --> click -->touchend
由此,我們可以在 ontouchstart 事件上記錄開始觸摸開始蕊爵,ontouchend 記錄觸摸結(jié)束信息辉哥。 通過上述這些參數(shù),很容易的去計算幽冥點擊的時間攒射,以及點擊穿透的相關(guān)信息醋旦,包括響應(yīng)的坐標(biāo)情況。