原文連結
(本翻譯未完全按照原文進行,因為老外太多廢話!)
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座標, 包含滾動條的滾動距離 |
width | pointer在螢幕上接觸範圍的寬度 |
height | pointer在螢幕上接觸範圍的高度 |
tiltX | 觸控介質沿Z軸方向與x軸的偏轉角度, x,y座標軸構成的平面為螢幕表面 |
tiltY | 觸控介質沿Z軸方向與y軸的偏轉角度, x,y座標軸構成的平面為螢幕表面 |
pressure | 按壓值 |
pointerType | Pointer 類別: 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 定義的核心事件:
事件型別 | 觸發時機 |
---|---|
pointerover | pointer移動到一個元素所在區域的時候 |
pointerenter | pointer 移動到一個元素或者其後代元素所在區域的時候. |
pointerdown | 啟用按鈕狀態 被賦值為非0值: 對於觸控或觸控筆是指和螢幕產生接觸的時候; 對於滑鼠是指一個按鍵被按下的時候 |
pointermove | pointer 改變了所在座標, 或者 按壓, 傾斜時,或者觸發了沒有在規範中定義的其他型別事件 |
pointerup | active buttons state 值為 left: 觸控筆或者手指離開螢幕, 或者釋放滑鼠按鍵 |
pointercancel | 檢測到當前pointer結束操作的時候, 比如改變方向, 觸控點太多等意外輸入 |
pointerout | pointer移出一個元素所在區域.在不支援hover的裝置上當 pointerup 事件和pointercancel 事件被觸發後觸發 |
pointerleave | pointer 離開元素或者起後代元素後 |
gotpointercapture | 當一個元素成為pointer的目標元素的時候 |
lostpointercapture | 當元素不在成為pointer的目標元素的時候 |
Mouse events, pointer events, 和touch events 對照表
Mouse event | Touch event | Pointer event |
---|---|---|
mousedown | touchstart | pointerdown |
mouseenter | pointerenter | |
mouseleave | pointerleave | |
mousemove | touchmove | pointermove |
mouseout | pointerout | |
mouseover | pointerover | |
mouseup | touchend | pointerup |
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 Events | Touch Events | Pointer Events | |
支援滑鼠 | Y | P | Y |
支援單點觸控 | P | Y | Y |
支援多點觸控 | N | Y | Y |
支援 筆, Kinect, 其他輸入裝置 | P | N | Y |
提供對 over/out/enter/leave events 和 hover 的支援 | Y | N | Y |
非同步 panning/zooming 硬體加速 | N | N | Y |
W3C 標準 | Y | Y | Y |
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”,更多前端開發知識