指针事件

Baseline 广泛可用 *

此特性已经十分成熟,可在许多设备和浏览器版本上使用。自 2020 年 7 月以来,它已在各大浏览器中可用。

* 此特性的某些部分可能存在不同级别的支持。

如今的许多 Web 内容都假设用户的指向设备是鼠标。然而,由于许多设备支持其他类型的指向输入设备,例如笔/手写笔和触摸屏,因此需要对现有的指向设备事件模型进行扩展。Pointer Events 解决了这一需求。

Pointer Events 是为指向设备触发的 DOM 事件。它们旨在创建一个统一的 DOM 事件模型,以处理鼠标、笔/手写笔或触摸(例如一个或多个手指)等指向输入设备。

指针(Pointer)是一种与硬件无关的设备,可以指向屏幕上的特定坐标集。拥有一个统一的指针事件模型可以简化网站和应用程序的创建,并提供良好的用户体验,无论用户使用何种硬件。但是,对于需要设备特定处理的场景,Pointer Events 定义了一个 pointerType 属性来检查产生事件的设备类型。

处理通用指针输入所需的事件类似于鼠标事件mousedown/pointerdownmousemove/pointermove 等)。因此,Pointer 事件类型与鼠标事件类型有意保持相似。

此外,Pointer 事件除了包含鼠标事件中常见的属性(客户端坐标、目标元素、按钮状态等)之外,还包含其他形式输入的新属性:压力、接触几何、倾斜等。事实上,PointerEvent 接口继承了所有 MouseEvent 属性,从而方便内容从鼠标事件迁移到 Pointer 事件。

术语

活动按钮状态

指针buttons 属性具有非零值时的条件。例如,对于笔,当笔与数字化仪物理接触时,或者在悬停时至少按下一个按钮。

活动指针

任何可以产生事件的指针输入设备。如果指针仍然可以产生进一步的事件,则认为它是活动的。例如,处于按下状态的笔被认为是活动的,因为它在笔抬起或移动时可以产生额外的事件。

数字化仪

一种具有可检测接触表面的传感设备。最常见的是,传感设备是支持触摸的屏幕,可以感应来自笔、手写笔或手指等输入设备的输入。一些传感设备可以检测输入设备的近距离,并且该状态表示为鼠标悬停。

命中测试

浏览器用于确定 Pointer 事件目标元素的过程。通常,这是通过考虑指针的位置以及文档中元素在屏幕媒体上的视觉布局来确定的。

pointer

一种与硬件无关的输入设备表示,可以指向屏幕上的特定坐标(或坐标集)。指针输入设备的示例包括鼠标、笔/手写笔和触摸接触。

指针捕获

指针捕获允许将指针的事件重新定向到特定元素,而不是指针位置的正常命中测试结果。有关示例,请参阅捕获指针

注意:指针捕获指针锁定不同,后者物理上阻止指针离开某个区域。

指针事件

指针触发的 DOM 事件

接口

主要接口是 PointerEvent 接口,它有一个 constructor 以及几种事件类型和相关的全局事件处理程序。

该标准还包括对 ElementNavigator 接口的一些扩展。

以下小节包含每个接口和属性的简短描述。

PointerEvent 接口

PointerEvent 接口扩展了 MouseEvent 接口并具有以下属性。

altitudeAngle 只读

表示传感器(指针或手写笔)轴与设备屏幕的 X-Y 平面之间的角度。

azimuthAngle 只读

表示 Y-Z 平面与包含传感器(指针或手写笔)轴和 Y 轴的平面之间的角度。

PointerEvent.persistentDeviceId 只读

生成 PointerEvent 的指向设备的唯一标识符。

pointerId 只读

导致事件的指针的唯一标识符。

width 只读

指针接触几何的宽度(X 轴上的大小),以 CSS 像素为单位。

height 只读

指针接触几何的高度(Y 轴上的大小),以 CSS 像素为单位。

pressure 只读

指针输入的归一化压力,范围为 01,其中 01 分别表示硬件能够检测到的最小和最大压力。

tangentialPressure 只读

指针输入的归一化切向压力(也称为桶压或圆柱应力),范围为 -11,其中 0 是控制器的中立位置。

tiltX 只读

Y-Z 平面与包含指针(例如笔手写笔)轴和 Y 轴的平面之间的平面角(以度为单位,范围为 -9090)。

tiltY 只读

X-Z 平面与包含指针(例如笔手写笔)轴和 X 轴的平面之间的平面角(以度为单位,范围为 -9090)。

twist 只读

指针(例如笔手写笔)围绕其主轴的顺时针旋转角度,以度为单位,值为 0359

pointerType 只读

指示导致事件的设备类型(鼠标、笔、触摸等)。

isPrimary 只读

指示此指针是否表示此指针类型的主要指针。

事件类型和全局事件处理程序

Pointer 事件有十种事件类型,其中七种与它们的鼠标事件对应项具有相似的语义(downupmoveoveroutenterleave)。

以下是每种事件类型的简短描述。

Event 描述
pointerover 当指针移入元素的命中测试边界时触发。
pointerenter 当指针移入元素或其后代元素的命中测试边界时触发,包括由于不支持悬停的设备(参见 pointerdown)触发 pointerdown 事件的结果。
pointerdown 当指针变为活动按钮状态时触发。
pointermove 当指针更改坐标时触发。如果指针状态的更改无法通过其他事件报告,则也使用此事件。
pointerup 当指针不再处于活动按钮状态时触发。
pointercancel 如果浏览器断定指针将不再能够生成事件(例如,如果相关设备被停用,或者浏览器决定将交互解释为平移/缩放),则会触发此事件。有关如何控制此行为的信息,请参阅下面的touch-action CSS 属性部分
pointerout 由于多种原因触发,包括:指针移出元素的命中测试边界;为不支持悬停的设备触发 pointerup 事件(参见 pointerup);在触发 pointercancel 事件之后(参见 pointercancel);当笔手写笔离开数字化仪可检测到的悬停范围时。
pointerleave 当指针移出元素的命中测试边界时触发。对于笔设备,当手写笔离开数字化仪可检测到的悬停范围时,会触发此事件。
pointerrawupdate 实验性 当指针更改任何不触发 pointerdownpointerup 事件的属性时触发。
gotpointercapture 当元素接收到指针捕获时触发。
lostpointercapture 为指针释放指针捕获后触发。

元素扩展

Element 接口有三个扩展

hasPointerCapture()

指示被调用的元素是否具有给定指针 ID 标识的指针捕获。

releasePointerCapture()

释放(停止)先前为特定指针事件设置的指针捕获

setPointerCapture()

将特定元素指定为未来指针事件的捕获目标

Navigator.maxTouchPoints 属性用于确定在任何给定时间点支持的最大同时触摸点数。

示例

本节包含使用 Pointer Events 接口基本用法的示例。

注册事件处理程序

此示例为给定元素的每种事件类型注册一个处理程序。

html
<div id="target">Touch me…</div>
js
function over_handler(event) {}
function enter_handler(event) {}
function down_handler(event) {}
function move_handler(event) {}
function up_handler(event) {}
function cancel_handler(event) {}
function out_handler(event) {}
function leave_handler(event) {}
function rawUpdate_handler(event) {}
function gotCapture_handler(event) {}
function lostCapture_handler(event) {}

const el = document.getElementById("target");
// Register pointer event handlers
el.onpointerover = over_handler;
el.onpointerenter = enter_handler;
el.onpointerdown = down_handler;
el.onpointermove = move_handler;
el.onpointerup = up_handler;
el.onpointercancel = cancel_handler;
el.onpointerout = out_handler;
el.onpointerleave = leave_handler;
el.onpointerrawupdate = rawUpdate_handler;
el.ongotpointercapture = gotCapture_handler;
el.onlostpointercapture = lostCapture_handler;

事件属性

此示例演示如何访问 Pointer 事件的所有属性。

html
<div id="target">Touch me…</div>
js
const id = -1;

function process_id(event) {
  // Process this event based on the event's identifier
}
function process_mouse(event) {
  // Process the mouse pointer event
}
function process_pen(event) {
  // Process the pen pointer event
}
function process_touch(event) {
  // Process the touch pointer event
}
function process_tilt(tiltX, tiltY) {
  // Tilt data handler
}
function process_pressure(pressure) {
  // Pressure handler
}
function process_non_primary(event) {
  // Non primary handler
}

function down_handler(ev) {
  // Calculate the touch point's contact area
  const area = ev.width * ev.height;

  // Compare cached id with this event's id and process accordingly
  if (id === ev.identifier) process_id(ev);

  // Call the appropriate pointer type handler
  switch (ev.pointerType) {
    case "mouse":
      process_mouse(ev);
      break;
    case "pen":
      process_pen(ev);
      break;
    case "touch":
      process_touch(ev);
      break;
    default:
      console.log(`pointerType ${ev.pointerType} is not supported`);
  }

  // Call the tilt handler
  if (ev.tiltX !== 0 && ev.tiltY !== 0) process_tilt(ev.tiltX, ev.tiltY);

  // Call the pressure handler
  process_pressure(ev.pressure);

  // If this event is not primary, call the non primary handler
  if (!ev.isPrimary) process_non_primary(ev);
}

const el = document.getElementById("target");
// Register pointerdown handler
el.onpointerdown = down_handler;

确定主要指针

在某些情况下,可能存在多个指针(例如,同时具有触摸屏和鼠标的设备),或者支持多个接触点(例如,支持多指触摸的触摸屏)的指针。应用程序可以使用 isPrimary 属性来识别每种指针类型的活动指针集中的主指针。如果应用程序只想支持主指针,则可以忽略所有非主指针事件。

鼠标只有一个指针,因此它始终是主指针。对于触摸输入,如果用户在没有其他活动触摸时触摸了屏幕,则认为该指针是主要的。对于笔和手写笔输入,如果用户在没有其他活动笔接触屏幕时最初接触了屏幕,则认为该指针是主要的。

确定按钮状态

某些指针设备(例如鼠标和笔)支持多个按钮,并且按钮按下可以和弦(即,在指针设备上的另一个按钮已经按下时按下附加按钮)。

为了确定按钮按下的状态,Pointer Events 使用 MouseEvent 接口(PointerEvent 继承自该接口)的 buttonbuttons 属性。

下表提供了各种设备按钮状态的 buttonbuttons 值。

设备按钮状态 button 按钮
自上次事件以来按钮或触摸/笔接触均未更改 -1
鼠标移动时未按下按钮,笔悬停时未按下按钮移动 0
左键鼠标、触摸接触、笔接触 0 1
中键鼠标 1 4
右键鼠标、笔杆按钮 2 2
X1(后退)鼠标 3 8
X2(前进)鼠标 4 16
笔擦除按钮 5 32

注意: button 属性表示按钮状态的变化。但是,在触摸的情况下,当一个事件发生多个事件时,所有事件都具有相同的值。

捕获指针

指针捕获允许将特定 指针事件 的事件重新定向到特定元素,而不是指针位置的正常命中测试。这可用于确保元素即使在指针设备的接触点移开元素(例如通过滚动或平移)后仍能继续接收指针事件。

指针捕获将导致目标捕获所有后续指针事件,就像它们发生在捕获目标上一样。因此,只要设置了此捕获,pointeroverpointerenterpointerleavepointerout不会触发。对于允许直接操作的触摸屏浏览器,当 pointerdown 事件触发时,将在元素上调用隐式指针捕获。可以通过在目标元素上调用 element.releasePointerCapture 手动释放捕获,或者在 pointeruppointercancel 事件之后隐式释放。

注意:如果您需要在 DOM 中移动元素,请务必在 DOM 移动之后调用 setPointerCapture(),这样 setPointerCapture() 才不会失去对它的跟踪。例如,如果您需要使用 Element.append() 将元素移动到其他位置,请务必仅在调用 Element.append() 之后才对其调用 setPointerCapture()

以下示例显示在元素上设置了指针捕获。

html
<div id="target">Touch me…</div>
js
function downHandler(ev) {
  const el = document.getElementById("target");
  // Element 'target' will receive/capture further events
  el.setPointerCapture(ev.pointerId);
}

const el = document.getElementById("target");
el.onpointerdown = downHandler;

以下示例显示指针捕获被释放(当发生 pointercancel 事件时)。当 pointeruppointercancel 事件发生时,浏览器会自动执行此操作。

html
<div id="target">Touch me…</div>
js
function downHandler(ev) {
  const el = document.getElementById("target");
  // Element "target" will receive/capture further events
  el.setPointerCapture(ev.pointerId);
}

function cancelHandler(ev) {
  const el = document.getElementById("target");
  // Release the pointer capture
  el.releasePointerCapture(ev.pointerId);
}

const el = document.getElementById("target");
// Register pointerdown and pointercancel handlers
el.onpointerdown = downHandler;
el.onpointercancel = cancelHandler;

touch-action CSS 属性

touch-action CSS 属性用于指定浏览器是否应将其默认(原生)触摸行为(例如缩放或平移)应用于某个区域。此属性可以应用于所有元素,除了:非替换的行内元素、表格行、行组、表格列和列组。

auto 值表示浏览器可以自由地将其默认触摸行为应用于(指定区域),none 值禁用浏览器对该区域的默认触摸行为。pan-xpan-y 值表示在该指定区域开始的触摸仅用于水平和垂直滚动。manipulation 值表示浏览器可以认为在该元素上开始的触摸仅用于滚动和缩放。

在以下示例中,某些 button 元素的默认触摸行为被禁用。

css
button#tiny {
  touch-action: none;
}

在以下示例中,当触摸 target 元素时,它将只在水平方向上平移。

css
#target {
  touch-action: pan-x;
}

与鼠标事件的兼容性

尽管 Pointer Event 接口使应用程序能够在支持指针的设备上创建增强的用户体验,但现实是,当今绝大多数 Web 内容都设计为仅与鼠标输入配合使用。因此,即使浏览器支持 Pointer Event,浏览器仍然必须处理鼠标事件,以便假定仅鼠标输入的内容无需直接修改即可正常工作。理想情况下,支持 Pointer Event 的应用程序不需要显式处理鼠标输入。但是,由于浏览器必须处理鼠标事件,因此可能需要处理一些兼容性问题。本节包含有关 Pointer Event 和鼠标事件交互以及对应用程序开发人员的影响的信息。

浏览器可以将通用指针输入映射到鼠标事件,以实现与基于鼠标的内容的兼容性。这种事件映射称为兼容性鼠标事件。作者可以通过取消 pointerdown 事件来阻止某些兼容性鼠标事件的产生,但请注意,

  • 鼠标事件只能在指针按下时阻止。
  • 悬停指针(例如,未按下任何按钮的鼠标)无法阻止其鼠标事件。
  • mouseovermouseoutmouseentermouseleave 事件永远不会被阻止(即使指针已按下)。

最佳实践

以下是使用 Pointer Event 时需要考虑的一些最佳实践

  • 最小化事件处理程序中执行的工作量。
  • 将事件处理程序添加到特定的目标元素(而不是整个文档或文档树中更高级别的节点)。
  • 目标元素(节点)应足够大以容纳最大的接触表面积(通常是手指触摸)。如果目标区域太小,触摸它可能会导致为相邻元素触发其他事件。

规范

规范
指针事件

浏览器兼容性

作为 Pointer Events 规范的一部分,已经为 CSS touch-action 属性定义了一些附加值,但目前这些值的实现支持有限。

另见