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

时间:2022-05-08
本文章向大家介绍[翻译]整合鼠标、触摸 和触控笔事件的,主要内容包括Html5 Pointer Event Api、Mouse events, pointer events, 和touch events 对照表、Mouse 兼容事件、Pointer API 的好处、Pointer Events 示例、多点触控、小结、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

Html5 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的如下属性更感兴趣:

唯一数值类型标识符

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

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

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

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相关操作的时候要谨慎。