[翻译]整合鼠标、触摸 和触控笔事件的Pointer Event Api

原文链接

https://mobiforge.com/design-development/html5-pointer-events-api-combining-touch-mouse-and-pen


(本翻译未完全按照原文进行,因为老外太多废话!)
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的如下属性更感兴趣:

t1.png

这里有几点需要注意的地方:

. <b>pointerId</b>:代表每一个独立的Pointer。根据id,我们可以很轻松的实现多点触控应用。
. <b>width/height</b>:Mouse Event 在屏幕上只能覆盖一个点的位置,但是一个Pointer可能覆盖一个更大的区域。
. <b>isPrimary</b>:当有多个Pointer被检测到的时候(比如多点触摸),对每一种类型的Pointer会存在一个Primary Poiter。只有Primary Poiter会产生与之对应的Mouse Event。稍后会讨论更多与此有关的内容。
. <b>pressure/tilt/width/height: </b>:这些特性,使程序支持更复杂的操作成为可能。


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

t2.png

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

t3.png

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的对比。

t4.png

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的<code>touchmove</code>事件一样,我们可以使用<code>pointermove</code>事件来处理移动事件。

下面我们设计我们的demo:当捕获一个pointerdown 事件的时候,我们开始追踪pointer的移动轨迹。所以我们首先要监听<code>pointerdown</code>事件,然后在<code>pointerdown</code>事件的处理函数中添加对<code>pointermove</code>事件的监听。

 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路径结束的时候——用户移开了手指或者笔尖,松开了鼠标按钮——我们需要停止绘图。所以我们需要监听<code>pointerup</code>事件,并添加一个<code>endPointer</code>处理函数。

 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”,更多前端开发知识

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容