指针事件(Pointer Events)是一种用于处理来自各种输入设备(如:鼠标、触控笔、触摸屏等)的输入信息的现代化解决方案

一段简史

很早以前,只存在鼠标事件

后来,触屏设备开始普及,尤其是手机和平板电脑。为了使现有的脚本仍能正常工作,它们生成(现在仍生成)鼠标事件。例如,轻触屏幕就会生成 mousedown 事件。因此,触摸设备可以很好地与网页配合使用

但是,触摸设备比鼠标具有更多的功能。例如,我们可以同时触控多点(多点触控)。然而,鼠标事件并没有相关属性来处理这些

因此,引入了触摸事件,例如 touchstarttouchend 和 touchmove,它们具有特定于触摸的属性(这里不再赘述这些特性,因为指针事件更加完善)

不过这还是不够完美。因为很多其他输入设备(如触控笔)都有自己的特性。而且同时维护两份分别处理鼠标事件和触摸事件的代码,显得有些笨重了

为了解决这些问题,引入了全新的规范——指针事件。它为各种指针输入设备提供了一套统一的事件

目前,各大主流浏览器已经支持了 Pointer Events Level 2 标准,版本更新的 Pointer Events Level 3 已经发布,并且大多数情况下与 Pointer Events Level 2 兼容

因此,除非你写的代码需要兼容旧版本的浏览器,例如 IE 10 或 Safari 12 或更低的版本,否则无需继续使用鼠标事件或触摸事件,而是使用指针事件

这样,代码就可以在触摸设备和鼠标设备上都能正常工作了

话虽如此,指针事件仍然有一些重要的奇怪特性,应当对它们有所了解以正确使用指针事件,并避免一些意料之外的错误,下文会对它们进行介绍

指针事件

指针事件的命名方式和鼠标事件类似:

指针事件 类似的鼠标事件
pointerdown mousedown
pointerup mouseup
pointermove mousemove
pointerover mouseover
pointerout mouseout
pointerenter mouseenter
pointerleave mouseleave
pointercancel -
gotpointercapture -
lostpointercapture -

不难发现,每一个 mouse<event> 都有与之相对应的 pointer<event>

用 pointer<event> 替换 mouse<event>

可以把代码中的 mouse<event> 都替换成 pointer<event>,程序仍然正常兼容鼠标设备

替换之后,程序对触屏设备的支持会“魔法般”地提升。但是,可能需要在 CSS 中的某些地方添加 touch-action: none,见 [[#指针中断]]

指针中断

pointercancel:一个正处于活跃状态的指针交互由于某些原因被中断时触发。也就是在这个事件之后,该指针就不会继续触发更多事件了

导致指针中断的可能原因如下:

例子:图片拖拽

  1. 用户按住了一张图片,开始拖拽 —— pointerdown 事件触发
  2. 用户开始移动指针(从而拖动图片)—— pointermove 事件持续触发
  3. 然后意料之外的情况发生了!浏览器有自己原生的图片拖放操作,接管了之前的拖放过程,于是触发了 pointercancel 事件

解决:阻止浏览器的默认行为来防止 pointercancel 触发

  1. 阻止原生的拖放操作发生:img.ondragstart = () => false
  2. 对于触屏设备,还有其他和触摸相关的浏览器行为(除了拖放)。为了避免它们所引发的问题:

指针捕获

elem.setPointerCapture(pointerId):将给定的 pointerId 绑定到 elem。在调用之后,所有具有相同 pointerId 的指针事件都将 elem 作为目标(就像事件发生在 elem 上一样)

绑定会在以下情况下被移除:

相关事件:

指针捕获可以被用于简化拖放类的交互,例如滑块:

![[Pasted image 20240430170127.png|500]]

实现逻辑:

  1. 用户按下滑动条的滑块 thumb —— pointerdown 事件触发
  2. 然后用户移动指针 —— pointermove 事件触发

不过,这并不是一个没有副作用的解决方案。问题是,指针在文档周围的移动可能会引起副作用:在其他元素上触发事件处理程序(例如 mouseover)并调用其他元素上与滑动条不相关的功能,这不是预期的效果

这就是指针捕获适用的场景:

因此,即使用户在整个文档上移动指针,事件处理程序也将仅在 thumb 上被调用。尽管如此,事件对象的坐标属性,例如 clientX/clientY 仍将是正确的,捕获仅影响 target/currentTarget

事件对象

![[Pasted image 20240430162214.png]]

PointerEvent 派生于 MouseEvent,拥有所有的 MouseEvent 特性,且增加了:

有些指针设备会测量接触面积和点按压力,可以使用以下属性: