網站首頁 編程語言 正文
概述
單擊事件是任何一個前端頁面中最常用的交互行為之一,在傳統的PC端大部分是使用click事件來實現用戶單擊交互的程序邏輯,而在移動Web端新增了touch事件來實現移動端更加敏感和復雜的觸摸交互行為。本章將就移動端touch事件的使用以及它與PC端的click事件的區別進行深入探討。
touch事件
在傳統的PC端,用戶的單擊操作主要是由鼠標的左鍵或者右鍵來產生,它主要是指鼠標的按鈕被按下,并且在很短的時間內(一般小于300ms)又被釋放開,這就被稱為單擊操作(或稱為一次點擊操作)。
而對于移動Web端,同樣也是如此,當手指觸摸到屏幕時開始計算時間,并且在300ms內離開屏幕,這段時間手指不能移動,這就算是移動Web端的單擊事件,手指觸摸就被稱為touch。
touch事件分類
移動Web端的touch觸摸事件主要由屏幕和觸摸點組成,其中屏幕可以是手機、平板或者觸摸板,而觸摸點可以通過手指、胳膊肘或觸摸筆,甚至耳朵、鼻子都行,但一般是通過手指。根據touch觸摸的類型可分為以下4種事件:
- touchstart:當手指與屏幕接觸時觸發。
- touchmove:當手指在屏幕上滑動時連續地觸發。
- touchend:當手指從屏幕上離開時觸發。
- touchcancel:當touch事件被迫終止,例如電話接入或者彈出信息時會觸發,或者當觸摸點太多,超過了支持的上限(自動取消早先的觸摸點)時觸發,一般不常用。
相比PC端,以上4種事件將用戶的touch行為劃分得更細,并且通過這些細化的事件可以實現移動Web端獨有的用戶交互行為,例如拖動swipe、長按longtap、雙指縮放pinch,等等。
其中的touchstart、touchmove和touchend是最常用的3個事件,其中touchstart最先觸發,touchend結束時觸發,而touchmove是否觸發取決于手指是否在觸摸屏上移動。下面用代碼來感受一下這3種事件的觸發順序,如下
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>touch事件</title> <script type="text/javascript"> document.addEventListener("touchstart",function () { console.log("開始觸摸"); }); document.addEventListener("touchmove",function () { console.log("移動手指"); }); document.addEventListener("touchend",function () { console.log("結束觸摸") }); </script> </head> <body> </body> </html>
在瀏覽器中運行這段代碼,同時注意要啟用Chrome中DevTools工具中的Device Mode功能,并使用鼠標模擬手指在屏幕上觸發觸摸事件,隨后就會在Console控制臺看到打印出對應的日志,從中可以看到一個簡單的觸摸操作是如何完成的。
touch事件對象
對于touch事件,每一次觸發都可以得到一個事件對象,在JavaScript中這個對象叫作TouchEvent,利用TouchEvent可以獲取touch事件觸發時的坐標、元素以及到底有幾個手指觸發等,下面就來了解一下TouchEvent事件對象。
可以在Console控制臺打印出當前觸發touch時的TouchEvent對象,代碼如下所示:
document.addEventListener("touchstart",function (e) { console.log(e); });
打印的內容如圖:
在上面的TouchEvent的屬性中,經常使用的就是touches、targetTouches和changedTouches,它們的含義分別是:
- touches:當前頁面(屏幕)上所有的觸摸點。
- targetTouches:當前綁定事件的元素上的觸摸點。
- changedTouches:當前屏幕上剛剛接觸的手指或者離開的手指的觸摸點。
這3個屬性返回的是TouchList對象,代表的是一個touch的集合數組,也就是說每一次touch觸發,都會兼顧到多指觸摸的場景,下面就分別以單指觸摸的場景和多指觸摸的場景來講解這3個屬性的區別。
首先是單指觸摸的場景,我們來模擬用戶一個手指觸摸,如圖。
外層的線框代表頁面,里面的一個<div>
元素綁定了touch事件,1號手指觸摸了該<div>
元素,這時touches、targetTouches以及changedTouches里面的觸摸點都是指1號手指這個觸摸點,應該很好理解。 對于多指觸摸的場景,條件是手指觸摸屏幕之后暫不離開,如圖。
外層的線框代表頁面,里面的一個<div>
元素綁定了touch事件,首先1號手指第一個觸摸了該<div>
元素,然后2號手指第二個也觸摸了該<div>
元素,最后3號手指第三個觸摸了div外面的區域,這時touches涵蓋的觸摸點的集合數組包括1號、2號、3號手指,而targetTouches涵蓋的觸摸點的集合數組包括1號和2號手指,而changedTouches涵蓋的觸摸點的集合數組包括2號和3號手指。
當手指都離開屏幕之后,touches和targetTouches中將不會再有值,changedTouches還會有一個值,此值為最后一個離開屏幕的手指的接觸點。這就是touches、targetTouches和changedTouches這3個屬性對于單指觸摸的場景和多指觸摸的場景下的區別,總結如下:
單指觸摸的場景:
- touches:1號手指
- targetTouches:1號手指
- changedTouches:1號手指
多指觸摸的場景:
- touches:1,2,3號手指
- targetTouches:1,2號手指
- changedTouches:2,3號手指
對于單指觸摸的場景來說,它們并無區別,主要區別在于多指觸摸的場景,所以在使用時可以根據具體的程序邏輯來選擇使用合適的屬性。
對于涵蓋觸摸點的集合數組TouchList而言,里面每個元素都是一個touch對象,通過這個對象可以獲取當前觸摸的位置,如圖。
其中,主要用到了offsetX/Y、pageX/Y和clientX/Y這3個屬性,它們的區別和含義分別是:
- offsetX/Y:觸摸位置相當于事件源元素的位置坐標,以當前
<div>
元素盒子模型的內容區域的左上角為原點。 - pageX/Y:觸摸位置相當于整個頁面內容區域的位置坐標,當頁面過長時,包括滾動隱藏的部分內容,以頁面完整內容區域的左上角為原點。
- clientX/Y:觸摸位置相當于瀏覽器視區(屏幕)區域的位置坐標,以相對于頁面的可見部分內容區域的左上角為原點。
具體的位置和距離可以參考下圖,外層表示頁面的所有內容,中間框表示瀏覽器的視區,其中有一個<div>
元素綁定了touch事件,黑點表示觸摸點的位置。
移動web單擊事件
在了解了touch事件之后,我們知道移動Web端的單擊事件完全可以由touchstart、touchmove和touchend來組合實現,移動Web端同時也提供了原生的click事件,它和傳統的PC端的click事件一樣,在用戶完成一次完整的手指單擊屏幕之后觸發。在移動Web端使用click綁定單擊事件,代碼如下:
document.addEventListener("click",function (e) { console.log(e); });
一切看似都很順利,在需要使用單擊時就用click事件,在需要使用touch時(拖動,長按等)就使用touch對應的事件。但是,對于移動Web端而言,處于iOS系統或Android系統時,采用click實現單擊事件卻有著不同的表現。
iOS單擊延遲
這要追溯至2007年初,蘋果公司在發布首款iPhone前遇到了一個問題:當時的網站都是為大屏幕設備所設計的,于是提出了視區(Viewport)的概念,其中一項即是用戶在瀏覽網頁時,可以在頁面的任何地方通過雙擊操作將頁面放大(Double Tap to Zoom)。這個交互功能提升了用戶瀏覽網頁時的體驗,于是Android和iOS的移動端瀏覽器紛紛支持了這個功能,但是對于雙擊這個操作而言,其實是包括了兩次單擊操作,當第一次單擊完成后,系統需要有一段時間來監聽是否有第二次單擊,如果有則表明此次操作是一個雙擊操作,而這段時間間隔大概有300毫秒(ms)。
因此,哪怕是只想要單擊這個事件,也都會經過雙擊放大這個判斷邏輯,導致要等到300毫秒之后才能收到單擊事件程序邏輯的反饋,這就是300毫秒的單擊延遲問題。
對于Android系統的瀏覽器而言,可以通過給視區設置user-scalable=no來禁止用戶進行縮放,隨后就可以正常地使用原生的click事件而沒有延遲;對于iOS系統而言,瀏覽器對user-scalable支持度存在Bug(漏洞),導致了無法通過簡單的設置來達到正常使用原生click事件的目的。代碼如下:
<meta name="viewport" content=" initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
所以,在iOS移動端,如果想要實現真正的單擊事件而沒有300毫秒延遲問題,就不能采用原生的click事件,可以通過touch(touchstart、touchmove和touchend)事件來模擬一次單擊操作。好在當前業界已有比較流行的方案,例如Zepto.js中的tap事件和FastClick.js庫可用來解決這個問題,在這里主要介紹一下FastClick.js庫。
FastClick.js是FT Labs團隊結合touch事件專門為解決移動端瀏覽器的300毫秒單擊延遲問題所開發的一個輕量級的庫。正常情況下,在移動Web端,當用戶單擊屏幕時,會依次觸發touchstart、touchmove(0 次或多次)、touchend、click(原生)這些事件。touchmove事件只有當手指在屏幕上移動時才會觸發。Touchstart、touchmove或者touchend 事件的任意一個調用event.preventDefault()方法,都會直接阻止原生click事件的觸發。
FastClick的實現原理是在檢測到touchend事件觸發時,把瀏覽器在300毫秒之后原生的click事件阻止掉,然后通過DOM自定義事件立即發出一個模擬的click事件,這樣就消除了300毫秒的延遲,提供了一個快速響應的“單擊”事件。如下代碼演示了FastClick的使用。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>FastClick.js</title> <script type="text/javascript" src="./fastclick.js"></script> </head> <body> <button id="click">點我</button> <script type="text/javascript"> // 頁面加載完成后,使用FastClick,一般傳遞最外層的body元素即可 document.addEventListener('DOMContentLoaded', function(){ FastClick.attach(document.body);// 在實際的項目中,需判斷在iOS移動端才需要此程序邏輯 }, false); document.getElementById("click").addEventListener("click",function(){ alert("單擊觸發! "); },false) </script> </body> </html>
需要注意的是,在不修改<meta>
標簽中的user-scalable屬性的情況下,300毫秒單擊延遲的問題只會出現在iOS系統的瀏覽器中,并且解決方案只需要針對iOS端,上文也提到了這個問題的產生是由于對user-scalable支持度存在Bug,之后蘋果公司也意識到了這個問題的嚴重性,于是在iOS 9.3版本時,提供了一個基于新的內核WKWebView的瀏覽器,并將其應用在Safari瀏覽器上,由此解決了這個問題(存在300毫秒單擊延遲問題的瀏覽器是UIWebView,這個內核已經不再維護了),并且后續使用iOS 9.3版本系統的瀏覽器在訪問頁面時,會默認使用WKWebView瀏覽器。
至此,移動Web端的300毫秒單擊延遲問題得到了徹底的改善。
“單擊穿透”問題
在移動Web端,有一個很常見的應用場景,單擊一個按鈕會出現一個蒙層,此蒙層是全屏遮蓋,并且有最高層級,當單擊蒙層時,蒙層消失。此場景和交互操作看似并沒有什么問題,但是假如頁面中有一個綁定了單擊事件的<div>
元素被蒙層遮蓋,而單擊蒙層關閉時的位置剛好和該<div>
元素重合,那么蒙層關閉后會同時觸發該<div>
元素的單擊事件,對于用戶來說,這個操作并不是要單擊該<div>
元素,這就是所謂的“單擊穿透”問題,如圖。
出現“單擊穿透”問題需要有個條件,即蒙層是通過綁定的touch事件來實現隱藏,而其遮蓋的<div>
元素綁定的是原生click事件,這樣就形成了touch事件觸發之后,蒙層隱藏了,300毫秒后當前這個觸摸點的click事件又觸發了,就形成“單擊穿透”。
移動Web端的“單擊穿透”問題出現的原因其實和300毫秒單擊延遲問題脫不了關系,但是“單擊穿透”出現的場景比較單一,并且也比較好解決。
解決“單擊穿透”問題可以從問題出現的原因上來著手,主要有以下兩種解決方案:
- 不要同時混用touch事件和click事件,要么給蒙層和
<div>
元素同時綁定touch事件,要么同時綁定click事件,在iOS 9.3版本之后,只用click事件即可,此方案體驗最好。 - 延遲蒙層消失的時間,例如在touch事件觸發后,在350毫秒后再讓蒙層消失,這樣后面的
<div>
元素就不會觸發click事件了,此方案會導致蒙層消失的響應慢,體驗差,并且有時會觸發兩次消失邏輯,故不推薦使用。
無論是300毫秒單擊延遲問題,還是“單擊穿透”問題,這些都是移動Web端特有的問題,也在一定程度上反映出移動Web端環境的復雜性,需要注意支持度和兼容性問題的地方很多,所以大家在進行移動Web端開發時,要有意識地去關注這些問題。
原文鏈接:https://juejin.cn/post/7138247179470110727
相關推薦
- 2022-09-13 Django?中使用日志的方法_python
- 2022-07-24 Git版本控制服務器詳解_其它綜合
- 2022-07-17 用Android?studio實現簡易計算器功能_Android
- 2022-07-26 Python+Seaborn繪制分布圖的示例詳解_python
- 2022-10-18 AJAX請求以及解決跨域問題詳解_AJAX相關
- 2022-07-13 kafka中Topic、消費組以及消息狀態詳解
- 2022-07-30 jQuery?UI組件介紹_jquery
- 2022-10-14 WebSecurityConfigurerAdapter已棄用
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支