從一次報錯聊聊 Point 事件

zhe.zhang發表於2017-05-14

同步自我的部落格,歡迎交流

這篇文章在草稿箱裡躺了很久,因為最近又遇到了相關問題,於是又整理了一下。請注意這裡講的不是 csspointer-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 事件,這個事件是用來處理什麼的呢?於是我帶著兩個問題開啟了搜尋之旅:

  1. 什麼是 pointerdown 事件
  2. 為什麼突然開始爆發錯誤

聊聊 pointer events

查問題,最簡單的問題就是摟一遍 W3C 的官方文件了。這裡簡單說下我的理解。

裝置輸入形式的多樣化

在 PC 時代,我們通過滑鼠與螢幕互動,這時候,我們設計系統時只需要考慮滑鼠事件就好了。但是如今,有很多新的裝置,比如智慧手機,平板電腦,他們包含了其他的輸入方式,比如觸控,手寫筆,官方也為這些輸入形式都提供了新的事件。

但是對於開發者來說,這是件很麻煩的事,因為這意味著你需要為你的網頁適配各種事件,比如你要根據使用者的移動來畫圖,你需要相容 PC 和手機,你的程式碼可能就會是下面這樣

dom.addEventListener('mousemove',
  draw);
dom.addEventListener('touchmove',
  draw);複製程式碼

如果需要相容更多的輸入裝置呢?比如手寫筆,這樣的話程式碼就會很複雜。而且,為了相容現有的基於滑鼠事件的程式碼,很多瀏覽器都會為所有的輸入型別觸發滑鼠事件(例如在 touchmove 時觸發 mousemove,我在 Chrome 試驗了一下不會觸發,但是因為沒有裝置,手寫筆的情況沒有試),這也會導致無法確認是否真的是滑鼠觸發的事件。

如何相容多種輸入形式

為了解決這一系列的問題,W3C 定義了一種新的輸入形式,即 pointer。任何由滑鼠、觸控、手寫筆或者其他輸入裝置在螢幕上觸發的接觸,都算是 pointer 事件。

從一次報錯聊聊 Point 事件

它的 API 和滑鼠事件很像,非常容易遷移。除了提供滑鼠事件常用的屬性,比如 clientXtarget 等等,還提供了一些用於其他輸入裝置的屬性,比如壓力,接觸面,傾斜角度等等,這樣開發者就可以利用 pointer 事件為所有的輸入裝置開發自己的功能了!

提供的屬性

pointer 事件提供了一些特有的事件屬性

  • pointerId:當前指標事件的唯一標識,主要是在多點觸控時標識唯一的一個輸入源
  • width:接觸面的寬度
  • height:接觸面的高度
  • pressure:接觸的壓力值,範圍是0-1,對於不支援壓力的硬體,比如滑鼠,按壓時該值必須為 0.5,否則為 0
  • tiltX,titltY:手寫筆的角度
  • pointerType:事件型別,目前有 mousepentouch,如果是無法探測的指標型別,則該值為空字串
  • isPrimary:用於標識是否是主指標,主要是在多點觸控中生效,開發者也可以通過忽略非主指標的指標事件來實現單點觸控。
    如何確定主指標:
    • 滑鼠輸入:一定是主指標
    • 觸控輸入:如果 pointerdown 觸發時沒有其他啟用的觸控事件,isPrimarytrue
    • 手寫筆輸入:與觸控事件類似,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 具體的支援資訊可以參考官方日誌,至於怎麼檢測瀏覽器支援,可以參考上面的內容

總結

  1. 對於開發來說,一定要鑽進去,任何 bug 都是有原因的
  2. 程式碼報錯應該有相應的監控機制,讓機器來幫我們發現問題,而不是靠人工去幹預

參考
www.w3.org/Submission/…
developers.google.com/web/updates…

相關文章