同步自我的部落格,歡迎交流
這篇文章在草稿箱裡躺了很久,因為最近又遇到了相關問題,於是又整理了一下。請注意這裡講的不是 css
的 pointer-events
。
起因
從某個月黑風高的晚上開始,有人發現我們的 web-app 在 Chrome 模擬器裡開始出現報錯,報錯資訊大概就是下面這樣。
VM1023:1 Uncaught TypeError: Cannot read property '0' of undefined複製程式碼
但是隻有他的瀏覽器有問題,而且對功能毫無影響,本著在我的機器上不復現的精神(好吧,當時比較忙),這個問題的優先順序排的不高,但是後面一段時間慢慢有人也出現相同的問題,於是我開始在意這個問題了。
定位問題
根據呼叫棧很快定位到了程式碼,原始碼定位到之前一位同事寫的元件程式碼,大概是這樣的:
dom.on('touchstart pointerdown', function (event) {
/*部分業務程式碼*/
var touch = event.touches[0]; //報錯的地方
/*部分業務程式碼*/
})複製程式碼
debug 發現是觸發了 pointdown
事件,因為 event
沒有 touches
這個欄位,導致丟擲異常。但是之前用的好好的呀,難道是瀏覽器的 API 變化了?而且我也沒有了解過 pointerdown
事件,這個事件是用來處理什麼的呢?於是我帶著兩個問題開啟了搜尋之旅:
- 什麼是
pointerdown
事件 - 為什麼突然開始爆發錯誤
聊聊 pointer events
查問題,最簡單的問題就是摟一遍 W3C
的官方文件了。這裡簡單說下我的理解。
裝置輸入形式的多樣化
在 PC 時代,我們通過滑鼠與螢幕互動,這時候,我們設計系統時只需要考慮滑鼠事件就好了。但是如今,有很多新的裝置,比如智慧手機,平板電腦,他們包含了其他的輸入方式,比如觸控,手寫筆,官方也為這些輸入形式都提供了新的事件。
但是對於開發者來說,這是件很麻煩的事,因為這意味著你需要為你的網頁適配各種事件,比如你要根據使用者的移動來畫圖,你需要相容 PC 和手機,你的程式碼可能就會是下面這樣
dom.addEventListener('mousemove',
draw);
dom.addEventListener('touchmove',
draw);複製程式碼
如果需要相容更多的輸入裝置呢?比如手寫筆,這樣的話程式碼就會很複雜。而且,為了相容現有的基於滑鼠事件的程式碼,很多瀏覽器都會為所有的輸入型別觸發滑鼠事件(例如在 touchmove
時觸發 mousemove
,我在 Chrome 試驗了一下不會觸發,但是因為沒有裝置,手寫筆的情況沒有試),這也會導致無法確認是否真的是滑鼠觸發的事件。
如何相容多種輸入形式
為了解決這一系列的問題,W3C 定義了一種新的輸入形式,即 pointer
。任何由滑鼠、觸控、手寫筆或者其他輸入裝置在螢幕上觸發的接觸,都算是 pointer
事件。
它的 API 和滑鼠事件很像,非常容易遷移。除了提供滑鼠事件常用的屬性,比如 clientX
,target
等等,還提供了一些用於其他輸入裝置的屬性,比如壓力,接觸面,傾斜角度等等,這樣開發者就可以利用 pointer
事件為所有的輸入裝置開發自己的功能了!
提供的屬性
pointer 事件提供了一些特有的事件屬性
- pointerId:當前指標事件的唯一標識,主要是在多點觸控時標識唯一的一個輸入源
- width:接觸面的寬度
- height:接觸面的高度
- pressure:接觸的壓力值,範圍是0-1,對於不支援壓力的硬體,比如滑鼠,按壓時該值必須為 0.5,否則為 0
- tiltX,titltY:手寫筆的角度
- pointerType:事件型別,目前有
mouse
,pen
,touch
,如果是無法探測的指標型別,則該值為空字串 - isPrimary:用於標識是否是主指標,主要是在多點觸控中生效,開發者也可以通過忽略非主指標的指標事件來實現單點觸控。
如何確定主指標:- 滑鼠輸入:一定是主指標
- 觸控輸入:如果
pointerdown
觸發時沒有其他啟用的觸控事件,isPrimary
為true
- 手寫筆輸入:與觸控事件類似,
pointerdown
觸發時沒有其他啟用的pointer
事件
相關事件
事件名稱 | 作用 |
---|---|
pointerover |
與 mouseover 行為一致 |
pointerenter |
與 mouseenter 行為一致 |
pointerdown |
指標進入活動狀態,比如觸控了螢幕,類似於 touchstart |
pointermove |
指標進行了移動 |
pointerup |
指標取消活動狀態,比如手指離開了螢幕,類似於 touchend |
pointercancel |
類似於 touchcancel |
pointerout |
指標離開元素邊緣或者離開螢幕,類似於 mouseout |
pointerleave |
類似於 mouseleave |
gotpointercapture |
元素捕獲到指標事件時觸發 |
lostpointercapture |
指標被釋放時觸發 |
可以看到,pointer
事件與已知的事件型別基本一致,但是有一點區別:在觸控式螢幕上,我們可能會滑動螢幕來觸發頁面滾動,縮放或者重新整理,對於 touch
事件,這時會觸發 touchmove
,但是對於 pointer
事件,當觸發這些瀏覽器行為時,你卻會接收到 pointercancel
事件以便於通知你瀏覽器已經接管了你的指標事件。
如何檢測
首先,pointer
事件的支援程度已經很不錯了,你可以使用 Pointer Events polyfill來進行相容,也可以自行檢測
if (window.PointerEvent) {
// 支援
} else {
// 不支援
}複製程式碼
導致問題的原因
這時候,對於本文一開始提到的問題就顯而易見了,因為 point events
是沒有 touches
這個屬性的。那麼我們還有兩個問題。
為什麼之前會用到 point events
?
後來我看了下 zepto
的原始碼,在事件處理時是考慮到了 point event
的,同事之前寫的程式碼大概是參考了 zepto
的事件系統。
為什麼會突然爆發這個問題?
很簡答,Chrome 55 開始支援這個 API,Chrome 具體的支援資訊可以參考官方日誌,至於怎麼檢測瀏覽器支援,可以參考上面的內容
總結
- 對於開發來說,一定要鑽進去,任何 bug 都是有原因的
- 程式碼報錯應該有相應的監控機制,讓機器來幫我們發現問題,而不是靠人工去幹預