指针事件
当今的大多数 Web 内容都假设用户的指向设备将是鼠标。但是,由于许多设备支持其他类型的指向输入设备,例如笔/触控笔和触摸屏,因此需要对现有的指向设备事件模型进行扩展。指针事件 解决了这一需求。
指针事件是针对指向设备触发的 DOM 事件。它们旨在创建一个单一的 DOM 事件模型来处理指向输入设备,例如鼠标、笔/触控笔或触摸(例如一个或多个手指)。
指针 是一个与硬件无关的设备,可以定位一组特定的屏幕坐标。对于指针使用单一的事件模型可以简化网站和应用程序的创建,并提供良好的用户体验,而不管用户的硬件如何。但是,对于需要特定设备处理的情况,指针事件定义了一个 pointerType
属性来检查产生事件的设备类型。
处理通用指针输入所需的事件类似于 鼠标事件(mousedown
/pointerdown
、mousemove
/pointermove
等)。因此,指针事件类型有意与鼠标事件类型相似。
此外,指针事件除了包含鼠标事件中通常存在的属性(客户端坐标、目标元素、按钮状态等)外,还包含其他输入形式的新属性:压力、接触几何形状、倾斜等。事实上,PointerEvent
接口继承了所有 MouseEvent
属性,从而促进了从鼠标事件到指针事件的内容迁移。
术语
活动按钮状态
当指针 的 buttons
属性具有非零值时的状态。例如,在笔的情况下,当笔与数字化仪发生物理接触时,或者在悬停时至少按下了一个按钮。
活动指针
任何可以产生事件的指针 输入设备。如果指针仍可以产生更多事件,则认为它处于活动状态。例如,处于按下状态的笔被认为是活动的,因为它在笔抬起或移动时可以产生其他事件。
数字化仪
一种具有可以检测接触的表面的传感设备。最常见的是,传感设备是触摸屏,可以感知来自输入设备(如笔、触控笔或手指)的输入。某些传感设备可以检测输入设备的接近程度,并且该状态表示为类似鼠标的悬停。
命中测试
浏览器用来确定指针事件的目标元素的过程。通常,这是通过考虑指针的位置以及屏幕媒体上文档中元素的视觉布局来确定的。
指针
输入设备的与硬件无关的表示,可以定位屏幕上特定的坐标(或一组坐标)。指针 输入设备的示例包括鼠标、笔/触控笔和触摸点。
指针捕获
指针事件
接口
主接口是 PointerEvent
接口,它具有 构造函数
以及多个事件类型和关联的全局事件处理程序。
该标准还包括对 Element
和 Navigator
接口的一些扩展。
以下子部分包含每个接口和属性的简短描述。
PointerEvent 接口
PointerEvent
接口扩展了 MouseEvent
接口,并具有以下属性。
altitudeAngle
只读 实验性-
表示换能器(指针或触控笔)轴与设备屏幕的 X-Y 平面之间的角度。
azimuthAngle
只读 实验性-
表示 Y-Z 平面与包含换能器(指针或触控笔)轴和 Y 轴的平面之间的角度。
PointerEvent.persistentDeviceId
只读 实验性-
生成
PointerEvent
的指向设备的唯一标识符。 pointerId
只读-
导致事件的指针的唯一标识符。
width
只读-
指针接触几何形状的宽度(X 轴上的大小),以 CSS 像素为单位。
height
只读-
指针接触几何形状的高度(Y 轴上的大小),以 CSS 像素为单位。
pressure
只读-
指针输入的归一化压力,范围为
0
到1
,其中0
和1
分别表示硬件能够检测到的最小和最大压力。 tangentialPressure
只读-
指针输入的归一化切向压力(也称为桶压力或圆柱应力),范围为
-1
到1
,其中0
是控件的中性位置。 tiltX
只读-
Y–Z 平面与包含指针(例如笔触控笔)轴和 Y 轴的平面之间的平面角(以度为单位,范围为
-90
到90
)。 tiltY
只读-
X–Z 平面与包含指针(例如笔触控笔)轴和 X 轴的平面之间的平面角(以度为单位,范围为
-90
到90
)。 twist
只读-
指针(例如笔触控笔)围绕其主轴的顺时针旋转角度(以度为单位),值范围为
0
到359
。 pointerType
只读-
指示导致事件的设备类型(鼠标、笔、触摸等)。
isPrimary
只读-
指示指针是否代表此指针类型的首要指针。
事件类型和全局事件处理程序
指针事件有十种事件类型,其中七种与鼠标事件的语义类似(down
、up
、move
、over
、out
、enter
和leave
)。
以下是每种事件类型的简要说明。
事件 | 描述 |
---|---|
pointerover |
当指针移动到元素的命中测试边界内时触发。 |
pointerenter |
当指针移动到元素或其后代元素的命中测试边界内时触发,包括由于不支持悬停的设备的pointerdown 事件导致的情况(参见pointerdown )。 |
pointerdown |
当指针变为活动按钮状态时触发。 |
pointermove |
当指针坐标发生变化时触发。如果指针状态的变化无法通过其他事件报告,则也会使用此事件。 |
pointerup |
当指针不再处于活动按钮状态时触发。 |
pointercancel |
如果浏览器得出结论认为指针将无法再生成事件(例如相关设备已停用),则会触发此事件。 |
pointerout |
由于多种原因触发,包括:指针移出元素的命中测试边界;为不支持悬停的设备触发pointerup事件(参见pointerup );在触发pointercancel 事件后(参见pointercancel );当笔式触控笔离开数字化仪可检测的悬停范围时。 |
pointerleave |
当指针移出元素的命中测试边界时触发。对于笔式设备,当触控笔离开数字化仪可检测的悬停范围时,会触发此事件。 |
pointerrawupdate 实验性 |
当指针更改任何不会触发pointerdown 或pointerup 事件的属性时触发。 |
gotpointercapture |
当元素接收指针捕获时触发。 |
lostpointercapture |
在释放指针的指针捕获后触发。 |
元素扩展
对Element
接口有三个扩展
hasPointerCapture()
-
指示调用它的元素是否对给定指针 ID 标识的指针具有指针捕获。
releasePointerCapture()
-
释放(停止)之前为特定指针事件设置的指针捕获。
setPointerCapture()
-
将特定元素指定为未来指针事件的捕获目标。
Navigator 扩展
Navigator.maxTouchPoints
属性用于确定在任何单个时间点支持的同时触控点的最大数量。
示例
本节包含使用指针事件接口的基本用法的示例。
注册事件处理程序
此示例为给定元素的每种事件类型注册一个处理程序。
<div id="target">Touch me…</div>
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) {}
function init() {
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;
}
document.addEventListener("DOMContentLoaded", init);
事件属性
此示例说明了访问指针事件的所有属性。
<div id="target">Touch me…</div>
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);
}
function init() {
const el = document.getElementById("target");
// Register pointerdown handler
el.onpointerdown = down_handler;
}
document.addEventListener("DOMContentLoaded", init);
确定主指针
在某些情况下,可能存在多个指针(例如,同时具有触摸屏和鼠标的设备),或支持多个接触点的指针(例如,支持多指触摸的触摸屏)。应用程序可以使用isPrimary
属性来识别每种指针类型的一组活动指针中的主指针。如果应用程序只想支持首要指针,则可以忽略所有非首要指针事件。
鼠标只有一个指针,因此它始终是首要指针。对于触摸输入,如果用户在没有其他活动触摸的情况下触摸屏幕,则该指针被视为首要指针。对于笔和触控笔输入,如果用户在没有其他活动笔接触屏幕的情况下最初接触屏幕,则该指针被视为首要指针。
确定按钮状态
某些指针设备(如鼠标和笔)支持多个按钮,并且可以合弦按下按钮(即,在指针设备上的另一个按钮已按下时按下另一个按钮)。
要确定按钮按下的状态,指针事件使用button
和buttons
MouseEvent
接口的属性(PointerEvent
继承自)。
下表提供了各种设备按钮状态的button
和buttons
的值。
设备按钮状态 | button | buttons |
---|---|---|
自上次事件以来,按钮和触摸/笔触都没有发生变化 | -1 |
— |
鼠标移动且未按下任何按钮,触控笔移动且未按下任何按钮 | — | 0 |
左键鼠标,触摸接触,触控笔接触 | 0 |
1 |
中键鼠标 | 1 |
4 |
右键鼠标,触控笔笔杆按钮 | 2 |
2 |
X1(后退)鼠标 | 3 |
8 |
X2(前进)鼠标 | 4 |
16 |
触控笔橡皮擦按钮 | 5 |
32 |
注意:button
属性指示按钮状态的变化。但是,就像触摸一样,当一个事件发生多个事件时,所有事件都具有相同的值。
捕获指针
指针捕获允许特定指针事件的事件重新定位到特定元素,而不是指针位置的正常命中测试。这可以用于确保即使指针设备的接触移出元素(例如通过滚动或平移),元素也能继续接收指针事件。
指针捕获将导致目标捕获所有后续指针事件,就好像它们发生在捕获目标上一样。因此,只要设置了此捕获,pointerover
、pointerenter
、pointerleave
和pointerout
将不会触发。对于允许直接操作的触摸屏浏览器,当pointerdown
事件触发时,将在元素上调用隐式指针捕获。可以通过在目标元素上调用element.releasePointerCapture
手动释放捕获,或者在pointerup
或pointercancel
事件后隐式释放。
注意:如果需要在 DOM 中移动元素,请确保在DOM 移动后调用setPointerCapture()
,以便setPointerCapture()
不会丢失对其的跟踪。例如,如果需要使用Element.append()
将元素移动到其他位置,请确保仅在调用Element.append()
后在该元素上调用setPointerCapture()
。
以下示例显示了在元素上设置指针捕获。
<div id="target">Touch me…</div>
function downHandler(ev) {
const el = document.getElementById("target");
// Element 'target' will receive/capture further events
el.setPointerCapture(ev.pointerId);
}
function init() {
const el = document.getElementById("target");
el.onpointerdown = downHandler;
}
document.addEventListener("DOMContentLoaded", init);
以下示例显示了指针捕获被释放(当pointercancel
事件发生时。当pointerup
或pointercancel
事件发生时,浏览器会自动执行此操作)。
<div id="target">Touch me…</div>
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);
}
function init() {
const el = document.getElementById("target");
// Register pointerdown and pointercancel handlers
el.onpointerdown = downHandler;
el.onpointercancel = cancelHandler;
}
document.addEventListener("DOMContentLoaded", init);
touch-action CSS 属性
touch-action
CSS 属性用于指定浏览器是否应将其默认(原生)触摸行为(如缩放或平移)应用于某个区域。此属性可以应用于除以下元素之外的所有元素:非替换内联元素、表格行、行组、表格列和列组。
auto
值表示浏览器可以自由地将其默认触摸行为应用于指定区域,而none
值则禁用浏览器对该区域的默认触摸行为。pan-x
和pan-y
值表示在指定区域开始的触摸仅用于水平和垂直滚动。manipulation
值表示浏览器可能会认为在元素上开始的触摸仅用于滚动和缩放。
在以下示例中,浏览器的默认触摸行为已针对div
元素禁用。
<html lang="en">
<body>
<div style="touch-action:none;">Can't touch this…</div>
</body>
</html>
在以下示例中,某些button
元素的默认触摸行为已禁用。
button#tiny {
touch-action: none;
}
在以下示例中,当触摸target
元素时,它将仅在水平方向上平移。
#target {
touch-action: pan-x;
}
与鼠标事件的兼容性
尽管指针事件接口使应用程序能够在支持指针的设备上创建增强的用户体验,但现实情况是,当今绝大多数 Web 内容的设计仅适用于鼠标输入。因此,即使浏览器支持指针事件,浏览器仍然必须处理鼠标事件,以便假设仅使用鼠标输入的内容无需直接修改即可按原样工作。理想情况下,支持指针的应用程序不需要显式处理鼠标输入。但是,由于浏览器必须处理鼠标事件,因此可能需要处理一些兼容性问题。本节包含有关指针事件和鼠标事件交互以及对应用程序开发人员的影响的信息。
浏览器可能会将通用指针输入映射到鼠标事件,以确保与基于鼠标的内容兼容。这种事件映射称为兼容性鼠标事件。作者可以通过取消pointerdown事件来阻止某些兼容性鼠标事件的产生,但请注意
- 只有在指针按下时才能阻止鼠标事件。
- 悬停指针(例如,未按下任何按钮的鼠标)无法阻止其鼠标事件。
mouseover
、mouseout
、mouseenter
和mouseleave
事件永远不会被阻止(即使指针按下)。
最佳实践
以下是一些使用指针事件时需要考虑的最佳实践
- 最大程度减少在事件处理程序中执行的工作量。
- 将事件处理程序添加到特定目标元素(而不是整个文档或文档树中较高级别的节点)。
- 目标元素(节点)应足够大以容纳最大的接触表面积(通常是手指触摸)。如果目标区域太小,触摸它可能会导致为相邻元素触发其他事件。
规范
规范 |
---|
指针事件 |
浏览器兼容性
BCD 表格仅在启用 JavaScript 的浏览器中加载。
作为指针事件规范的一部分,已为 CSS touch-action
属性定义了一些其他值,但目前这些值的支持实现有限。