向前邁進

Sérgio Gomes

請看著網路上的內容,這以前是非常簡單的。你一個人移動了滑鼠、移動的按鈕 有時則是按下按鈕的按鈕所有非滑鼠都得到的模擬結果都經過模擬,開發人員也知道該以什麼為核心。

但簡單不見得是好事。隨著時間的推移,一切都變得越來越重要:不是任何東西都會 (或假裝成) 點擊:你可以利用壓力感應和傾斜感知筆,實現令人讚嘆的創作自由。只要用手指就能完成所有所需工作,只需動動裝置即可,另外還有一點:為何,當著單指時,不要使用超過一根手指。

我們有一段時間的觸控事件是為瞭解決這個問題,不過這些事件是完全獨立的 API 專屬 API,因此需要您編寫兩個不同的事件模型,才能同時支援滑鼠和觸控功能。Chrome 55 內建新的標準,可用於整合這兩種模型:指標事件。

單一事件模型

指標事件會統整瀏覽器的指標輸入模型,將觸控、筆和滑鼠整合為一組事件。例如:

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

以下是所有可用事件的清單,如果您熟悉滑鼠事件,應該十分熟悉:

pointerover 指標已進入元素的定界框。如果裝置支援懸停功能,就會立即發生這個問題;如果裝置不支援懸停,則會在 pointerdown 事件之前發生。
pointerenter pointerover 類似,但不會以不同方式彈出及處理子系。規格詳細資訊
pointerdown 指標已進入有效按鈕狀態,視輸入裝置的語意而定,可能是按下按鈕或建立聯絡人。
pointermove 指標位置已變更。
pointerup 指標已離開使用中的按鈕狀態。
pointercancel 發生某種狀況,代表指標不太可能發出更多事件。這表示您應取消任何進行中的動作,並回到中立的輸入狀態。
pointerout 指標離開元素或畫面的定界框。此外,在 pointerup 之後,如果裝置不支援懸停功能,
pointerleave pointerout 類似,但不會以不同方式彈出及處理子系。規格詳細資訊
gotpointercapture 元素已收到指標擷取
lostpointercapture 已發布正在擷取的指標。

不同的輸入類型

一般來說,指標事件可讓您以任何輸入方式編寫程式碼,而不必為不同的輸入裝置註冊個別的事件處理常式。當然,您還是必須瞭解輸入類型之間的差異,例如是否適用懸停的概念。如果您想區分不同的輸入裝置類型 (例如為不同的輸入內容提供獨立程式碼/功能),可以在相同的事件處理常式中使用 PointerEvent 介面的 pointerType 屬性進行操作。舉例來說,假設您要編寫側邊導覽匣的程式碼,可以在 pointermove 事件中加入下列邏輯:

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

預設動作

在支援觸控功能的瀏覽器中,使用者會使用某些手勢來捲動、縮放或重新整理頁面。 使用觸控事件時,您仍會在這些預設動作發生時收到事件。舉例來說,當使用者捲動畫面時,系統仍會觸發 touchmove

使用指標事件時,每當觸發捲動或縮放等預設動作,您就會收到 pointercancel 事件,指出瀏覽器已控制指標。例如:

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

內建速度:相較於觸控事件,這個模型預設有助於提高效能,使用被動事件監聽器才能達到同等的回應等級。

您可以使用 touch-action CSS 屬性,讓瀏覽器停止控制。在元素上設為 none 後,就會停用在該元素上啟動的所有瀏覽器定義動作。不過,還有一些其他值可以使用更精細的控制項 (例如 pan-x),可讓瀏覽器回應 x 軸上的動作,但不支援 Y 軸。Chrome 55 支援下列值:

auto 預設設定;瀏覽器可執行任何預設動作。
none 瀏覽器不得執行任何預設動作。
pan-x 瀏覽器只能執行水平捲動預設動作。
pan-y 瀏覽器只能執行垂直捲動的預設動作。
pan-left 瀏覽器只能執行水平捲動預設動作,而且只能將頁面向左平移。
pan-right 瀏覽器只能執行水平捲動預設動作,而且只能將頁面向右平移。
pan-up 瀏覽器只能執行垂直捲動預設動作,而且只能向上平移頁面。
pan-down 瀏覽器只能執行垂直捲動的預設動作,而且只能向下平移頁面。
manipulation 瀏覽器只能執行捲動和縮放動作。

指標擷取

您是否曾經在為損壞的 mouseup 事件進行偵錯上花費太多時間,直到發現這種情況是因為使用者離開點擊目標以外的原因?沒有?好吧,也許他們就是我。

不過,到目前為止,還是有些解決方法未能妥善處理。沒問題,您可以在文件上設定 mouseup 處理常式,並在應用程式中儲存一些狀態來追蹤活動。但這不是最乾淨的解決方案,尤其在建構網頁元件並嘗試保持一切整齊且隔離的情況下,更是如此。

使用指標事件是更好的解決方式:您可以擷取指標,確保您取得 pointerup 事件 (或任何其他不忠實的好友)。

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

瀏覽器支援

撰寫本文時,Internet Explorer 11、Microsoft Edge、Chrome 和 Opera 支援指標事件事件,且 Firefox 僅支援部分支援指標。您可以前往 caniuse.com 查看最新的清單

您可以使用遊標事件 polyfill 來填入缺口。此外,您也可以在執行階段中直接檢查瀏覽器支援:

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

指標事件是漸進式強化的絕佳選項:只要修改初始化方法進行上述檢查、在 if 區塊中新增指標事件處理常式,以及將滑鼠/觸控事件處理常式移至 else 區塊即可。

因此,歡迎踴躍試用,並與我們分享您的想法!