[翻譯]整合滑鼠、觸控 和觸控筆事件的Html5 Pointer Event Api

玄魂發表於2017-06-24

原文連結

mobiforge.com/design-deve…


(本翻譯未完全按照原文進行,因為老外太多廢話!)
Pointer Events API 是Hmtl5的事件規範之一,它主要目的是用來將滑鼠(Mouse)、觸控(touch)和觸控筆(pen)三種事件整合為統一的API。

相比Touch Events API,雖然目前除了Apple的 Safari瀏覽器,其他瀏覽器都在實現對該事件型別的支援,但是情況並不是很好。
本篇文章忽略瀏覽器的相容問題,只討論其基本使用方法。更多內容可以參考:Pointer Events 背景資料

Pointer Events

和 Touch Events API 對應於觸控事件類似,Pointer Events API則對應於Pointer事件,那麼什麼是Pointer呢?

Pointer 是指可以在螢幕上反饋一個指定座標的輸入裝置。

Pointer Events繼承並擴充套件了Mouse Event,所以它擁有Mouse Event的常用屬性,比如 clientX, clientY等等,同時也增加了一些新的屬性,比如tiltX, tiltY, 和 pressure等等。我們對Pointer的如下屬性更感興趣:






























































屬性說明
pointerId唯一數值型別識別符號
screenX相對於使用者螢幕的x座標
screenY相對於使用者螢幕的y座標
clientX相對於當前視窗的x座標,不包含滾動條的滾動距離
clientY相對當前視窗的y座標,不包含滾動條的滾動距離
pageX相對於頁面的x座標, 包含滾動條的滾動距離
pageY相對於頁面的y座標, 包含滾動條的滾動距離
widthpointer在螢幕上接觸範圍的寬度
heightpointer在螢幕上接觸範圍的高度
tiltX觸控介質沿Z軸方向與x軸的偏轉角度, x,y座標軸構成的平面為螢幕表面
tiltY觸控介質沿Z軸方向與y軸的偏轉角度, x,y座標軸構成的平面為螢幕表面
pressure按壓值
pointerTypePointer 類別: mouse, pen, 或者touch
isPrimary是否是主Pointer裝置

這裡有幾點需要注意的地方:

. pointerId:代表每一個獨立的Pointer。根據id,我們可以很輕鬆的實現多點觸控應用。\
. width/height:Mouse Event 在螢幕上只能覆蓋一個點的位置,但是一個Pointer可能覆蓋一個更大的區域。\
. isPrimary:當有多個Pointer被檢測到的時候(比如多點觸控),對每一種型別的Pointer會存在一個Primary Poiter。只有Primary Poiter會產生與之對應的Mouse Event。稍後會討論更多與此有關的內容。\
. pressure/tilt/width/height: :這些特性,使程式支援更復雜的操作成為可能。


下面是PointerEvent Api 定義的核心事件:














































事件型別觸發時機
pointeroverpointer移動到一個元素所在區域的時候
pointerenterpointer 移動到一個元素或者其後代元素所在區域的時候.
pointerdown啟用按鈕狀態 被賦值為非0值: 對於觸控或觸控筆是指和螢幕產生接觸的時候; 對於滑鼠是指一個按鍵被按下的時候
pointermovepointer 改變了所在座標, 或者 按壓, 傾斜時,或者觸發了沒有在規範中定義的其他型別事件
pointerupactive buttons state 值為 left: 觸控筆或者手指離開螢幕, 或者釋放滑鼠按鍵
pointercancel檢測到當前pointer結束操作的時候, 比如改變方向, 觸控點太多等意外輸入
pointeroutpointer移出一個元素所在區域.在不支援hover的裝置上當 pointerup 事件和pointercancel事件被觸發後觸發
pointerleavepointer 離開元素或者起後代元素後
gotpointercapture當一個元素成為pointer的目標元素的時候
lostpointercapture當元素不在成為pointer的目標元素的時候

Mouse events, pointer events, 和touch events 對照表










































Mouse eventTouch eventPointer event
mousedowntouchstartpointerdown
mouseenterpointerenter
mouseleavepointerleave
mousemovetouchmovepointermove
mouseoutpointerout
mouseoverpointerover
mouseuptouchendpointerup

Mouse Event 和Point Event做一個對等關係很容易,但是Touch Event就沒那麼樂觀了。但是上面的表格只是一個粗略的對照關係,相對應的事件在具體實現和含義上並不完全相同。這意味著你不能使用同一個處理函式來處理不同型別的事件,除非你明確的知道你在幹什麼,因為這些事件的運作方式不同。例如touchmove 事件的目標元素是touch began 時的元素,即使move的過程中觸點不在該元素區域內,touchemove的目標元素仍然不會改變;但是mousemove 和 pointermove的目標元素是位於觸點下方的元素,離開該元素區域,目標元素就會改變。

Mouse 相容事件

Poiter API的強大之處在於它對Mouse Event的相容,使得基於Mouse Event的站點可以很好的執行。當然這是有意為之,為了達到這個目的,當Pointer Event被觸發之後,會再次觸發一個對應的Mouse Event。當然只有被認定為主Pointer(primary Pointer)的Pointer才會繼續觸發Mouse Event。某種程度上,你可以認為在同一時間只有一個滑鼠輸入。基於Mouse Event 的網站,原有的處理邏輯無需改動,只需要新增新的針對Touch Event的處理邏輯即可。

Pointer API 的好處

Poiter API 整合了滑鼠、觸控和觸控筆的輸入,使得我們無需對各種型別的事件區分對待。

目前不論是web還是本地應用都被設計成跨終端(手機,平板,PC)應用,滑鼠多數應用在桌面應用,觸控則出現在各種裝置上。過去開發人員必須針對不同的輸入裝置寫不同的程式碼,或者寫一個polyfill 來封裝不同的邏輯。Pointer Events 改變了這種狀況:

統一事件監聽,不用再分別處理\
不用為獲取不同事件的座標值寫不同的程式碼\
如果輸入裝置支援,可以獲取壓力、寬、高、傾斜角度等引數\
如果需要的話可以區別對待不同是事件型別

下面是各種事件Api的對比。




















































 Mouse EventsTouch EventsPointer Events
支援滑鼠YPY
支援單點觸控PYY
支援多點觸控NYY
支援 筆, Kinect, 其他輸入裝置PNY
提供對 over/out/enter/leave events 和 hover 的支援YNY
非同步 panning/zooming 硬體加速NNY
W3C 標準YYY

Pointer Events 示例

在本篇文章中,我們只展示Pointer Event Api的基本使用。第一件要做的事情是檢測瀏覽器是否支援Pointer Event。

瀏覽器支援校驗

if (window.PointerEvent) { 
  // Pointer events are supported. 
}複製程式碼

事件監聽

第一個demo,我們建立Pointer Event的事件監聽程式,列印輸入點的座標值。我們建立兩個div,一個用來捕獲Pointer事件,另一個用來展現座標值。

<div id="coords"></div>
  <div id="pointerzone"></div>複製程式碼

接下來新增事件監聽的程式碼:

  function init() {
    // Get a reference to our pointer div
    var pointerzone = document.getElementById("pointerzone");
    // Add an event handler for the pointerdown event
    pointerzone.addEventListener("pointerdown", pointerHandler, false);
}複製程式碼

在pointerHandler函式中,獲取並展現pointer事件的座標值:

  function pointerHandler(event) {
    // Get a reference to our coordinates div
    var coords = document.getElementById("coords");
    // Write the coordinates of the pointer to the div
    coords.innerHTML = 'x: ' + event.pageX + ', y: ' + event.pageY;
  }複製程式碼

我們確保在頁面載入完成後執行init函式。

   <body onload="init()">
  ...
  </body>
  }複製程式碼

現在可以在瀏覽器開啟頁面了,如果你的瀏覽器支援pointer event,單擊滑鼠,就可以在頁面看到輸出的座標值了。

pointermove event

和使用touch api的touchmove事件一樣,我們可以使用pointermove事件來處理移動事件。

下面我們設計我們的demo:當捕獲一個pointerdown 事件的時候,我們開始追蹤pointer的移動軌跡。所以我們首先要監聽pointerdown事件,然後在pointerdown事件的處理函式中新增對pointermove事件的監聽。

 canvas.addEventListener("pointerdown", function() {
    canvas.addEventListener("mousemove", drawpointermove, false);
  }
  , false);複製程式碼

在drawpointermove函式中,我們根據前後兩個點的座標,來連續繪製軌跡。

function draw(e) {
  ctx.beginPath();
  // Start at previous point
  ctx.moveTo(lastPt.x, lastPt.y);
  // Line to latest point
  ctx.lineTo(e.pageX, e.pageY);
  // Draw it!
  ctx.stroke();

  //Store latest pointer
  lastPt = {x:e.pageX, y:e.pageY};
}複製程式碼

當pointer路徑結束的時候——使用者移開了手指或者筆尖,鬆開了滑鼠按鈕——我們需要停止繪圖。所以我們需要監聽pointerup事件,並新增一個endPointer處理函式。

 canvas.addEventListener("pointerup", endPointer, false);

  function endPointer(e) {
    //Stop tracking the pointermove event
    canvas.removeEventListener("pointermove", drawpointermove, false); 

    //Set last point to null to end our pointer path
    lastPt = null;
  }複製程式碼

執行結果:

下面給出這個demo的完整程式碼:

<html>
      <head>
        <style>
          /* Disable intrinsic user agent touch behaviors (such as panning or zooming) */
          canvas {
            touch-action: none;
          }
        </style>


      <script type='text/javascript'>
        var lastPt = null;
        var canvas;
        var ctx;

        function init() {
          canvas = document.getElementById("mycanvas");
          ctx = canvas.getContext("2d");
          var offset  = getOffset(canvas);
          if(window.PointerEvent) {
            canvas.addEventListener("pointerdown", function() {
              canvas.addEventListener("pointermove", draw, false);
            }
            , false);
            canvas.addEventListener("pointerup", endPointer, false);
          }
          else {
            //Provide fallback for user agents that do not support Pointer Events
            canvas.addEventListener("mousedown", function() {
              canvas.addEventListener("mousemove", draw, false);
            }
            , false);
            canvas.addEventListener("mouseup", endPointer, false);
          }
        }

        // Event handler called for each pointerdown event:
        function draw(e) {
          if(lastPt!=null) {
            ctx.beginPath();
            // Start at previous point
            ctx.moveTo(lastPt.x, lastPt.y);
            // Line to latest point
            ctx.lineTo(e.pageX, e.pageY);
            // Draw it!
            ctx.stroke();
          }
          //Store latest pointer
          lastPt = {x:e.pageX, y:e.pageY};
        }

        function getOffset(obj) {
          //...
        }

        function endPointer(e) {
          //Stop tracking the pointermove (and mousemove) events
          canvas.removeEventListener("pointermove", draw, false); 
          canvas.removeEventListener("mousemove", draw, false); 

          //Set last point to null to end our pointer path
          lastPt = null;
        }

      </script>
    </head>
    <body onload="init()">
      <canvas id="mycanvas" width="500" height="500" style="border:1px solid black;"></canvas>
    </body>
  </html>複製程式碼

多點觸控

這個例子中,我們擴充套件上面的pointmove事件的程式碼,來實現對多點觸控的支援。

首先我們初始一個多個顏色的陣列,用來追蹤不同的pointer。

var colours = ['red', 'green', 'blue', 'yellow','black'];複製程式碼

畫線的時候通過pointer的id來取色。

 //Key the colour based on the id of the Pointer
  multitouchctx.strokeStyle = colours[id%5];
  multitouchctx.lineWidth = 3;複製程式碼

在這個demo中,我們要追蹤每一個pointer,所以需要分別儲存每一個pointer的相關座標點。這裡我們使用關聯陣列來儲存資料,每個資料使用pointerId做key,我們使用一個Object物件作為關聯陣列,用如下方法新增資料:

// This will be our associative array
var multiLastPt=new Object();
...
// Get the id of the pointer associated with the event
var id = e.pointerId;
...
// Store coords
multiLastPt[id] = {x:e.pageX, y:e.pageY};複製程式碼

結束畫線的時候,需要刪除相關資料。

  delete multiLastPt[id];複製程式碼

執行結果如下:

完整程式碼如下:

<!DOCTYPE html>
<html>
  <head>
  <title>HTML5 multi-touch</title>
    <style>
     canvas {
       touch-action: none;
     }
    </style>
    <script>
    var canvas;
    var ctx;
    var lastPt = new Object();
    var colours = ['red', 'green', 'blue', 'yellow', 'black'];

    function init() {
      canvas = document.getElementById('mycanvas');
      ctx = canvas.getContext("2d");

          if(window.PointerEvent) {
            canvas.addEventListener("pointerdown", function() {
              canvas.addEventListener("pointermove", draw, false);
            }
            , false);
            canvas.addEventListener("pointerup", endPointer, false);
          }
          else {
            //Provide fallback for user agents that do not support Pointer Events
            canvas.addEventListener("mousedown", function() {
              canvas.addEventListener("mousemove", draw, false);
            }
            , false);
            canvas.addEventListener("mouseup", endPointer, false);
          }
    }

    function draw(e) {
      var id = e.pointerId;   
      if(lastPt[id]) {
        ctx.beginPath();
        ctx.moveTo(lastPt[id].x, lastPt[id].y);
        ctx.lineTo(e.pageX, e.pageY);
        ctx.strokeStyle = colours[id%5];
        ctx.stroke();
      }
      // Store last point
      lastPt[id] = {x:e.pageX, y:e.pageY};
    }

    function endPointer(e) {
      var id = e.pointerId;

      canvas.removeEventListener("mousemove", draw, false); 
      // Terminate this touch
      delete lastPt[id];
    }  

  </script>
  </head>
  <body onload="init()">
    <canvas id="mycanvas" width="500" height="500">
      Canvas element not supported.
    </canvas>
  </body>
</html>複製程式碼

小結

本文只是簡單介紹了Pointer Event的使用,雖然目前瀏覽器的支援情況並不完美,但是作為w3c的標準,會被支援的越來越好。

如果你在開發中使用Pointer Event Api,一定要注意它和touch事件的區別,處理touch相關操作的時候要謹慎。


歡迎關注玄魂工作室--訂閱號回覆“html5”,更多前端開發知識

相關文章