Pointer events - 扩展了指针设备事件模型
今天的大部分网络内容都假设用户的指点设备是鼠标。然而,由于许多设备支持其他类型的指点输入设备,例如触控笔,手写笔和触摸面板,因此需要对现有指针设备事件模型进行扩展。指针事件解决了这个需求。
注意: Web Workers 中的指针事件不可用。
指针事件是为指针设备触发的 DOM 事件。它们旨在创建单个 DOM 事件模型来处理指向输入设备,例如鼠标、笔 / 触笔或触摸(例如一根或多根手指)。
指针是一种与硬件无关的设备,可以针对一组特定的屏幕坐标。拥有单一的指针事件模型可以简化创建 Web 站点和应用程序的过程,并提供良好的用户体验,而不管用户的硬件如何。但是,对于需要特定于设备的处理的场景,指针事件定义了一个 pointerType
属性来检查产生事件的设备类型。
处理通用指针输入所需的事件类似于鼠标事件
(mousedown
/pointerdown
、mousemove
/pointermove
等等)。因此,指针事件类型有意类似于鼠标事件类型。
此外,指针事件包含鼠标事件中的常用属性(客户端坐标、目标元素、按钮状态等),此外还有其他形式的输入的新属性:压力、接触几何、倾斜等。事实上,PointerEvent
接口继承了 MouseEvent
所有的属性,因此便于将内容从鼠标事件迁移到指针事件。
术语
活动按钮状态
当指针的 buttons
属性值为非零值时,表示活动状态。例如,在有笔的情况下,当笔与数字化仪有物理接触时,或在悬停时按下至少一个按钮。
活动指针
任何可以产生事件的指针输入设备。如果指针仍然可以产生更多事件,则它被认为是活动的。例如,处于按下状态的笔被认为是活动的,因为当笔被提起或移动时,它会产生额外的事件。
数字化仪
一种表面可以检测接触的传感设备。最常见的是,感应设备是启用触摸的屏幕,可以感应来自输入设备(例如笔、触控笔或手指)的输入。一些传感设备可以检测到输入设备的靠近,状态表现为跟随鼠标的悬停。
命中测试
浏览器用于确定指针事件的目标元素的过程。通常,这是通过考虑指针的位置以及屏幕媒体上文档中元素的视觉布局来确定的。
指针
表示与硬件无关的输入设备,可以指向屏幕上的特定坐标(或坐标集)。指针输入设备的实例是鼠标、笔 / 手写笔和触摸面板。
指针捕获
指针捕获允许将指针的事件重定向到特定元素,而不是指针位置的正常命中测试结果。
指针事件
接口
主要的接口是 PointerEvent
,它有一个构造器
,加上几个事件类型和相关的全局事件处理程序。
该标准还包括对 Element
和 Navigator
接口的一些扩展。
以下小节包含每个接口和属性的简短描述。
PointerEvent
接口
PointerEvent
接口扩展了 MouseEvent
接口并具有以下属性。(以下所有属性都是 只读 。)
pointerId
触发事件的指针的唯一标识符。
width
指针接触几何图形的宽度(X 轴上的大小),以 CSS 像素为单位。
height
指针接触几何图形的高度(Y 轴上的大小),以 CSS 像素为单位。
pressure
指针输入的归一化压力,范围为 0
到 1
,其中 0
和 1
分别代表硬件能够检测的最小和最大压力。
tangentialPressure
指针输入的归一化切向压力(也称为桶压力(barrel pressure)或气缸应力(cylinder stress)),范围为 -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 |
onpointerover |
当指针移动到元素的命中测试边界时触发。 |
pointerenter |
onpointerenter |
当指针移动到元素或其后代之一的命中测试边界时触发,包括来自不支持悬停的设备的 pointerdown 事件的结果(参见 pointerdown )。 |
pointerdown |
onpointerdown |
当指针变为活动按钮状态时触发。 |
pointermove |
onpointermove |
当指针改变坐标时触发。如果其他事件无法报告指针状态的变化,也会使用此事件。 |
pointerup |
onpointerup |
当指针不再是活动按钮状态时触发。 |
pointercancel |
onpointercancel |
如果浏览器断定指针将不再能够生成事件(例如,相关设备已停用),则会触发此事件。 |
pointerout |
onpointerout |
出于多种原因触发,包括:指针移出元素的命中测试边界;为不支持悬停的设备触发 pointerup 事件(请参阅 pointerup);在触发 pointercancel 事件后(参见 pointercancel );当触控笔离开数字化仪可检测的悬停范围时。 |
pointerleave |
onpointerleave |
当指针移出元素的命中测试边界时触发。对于笔设备,当触控笔离开数字化仪可检测到的悬停范围时会触发此事件。 |
gotpointercapture |
ongotpointercapture |
当元素接收到指针捕获时触发。 |
lostpointercapture |
onlostpointercapture |
在为指针释放指针捕获后触发。 |
元素扩展
Element
接口有三个扩展:
setPointerCapture()
指定一个特定元素作为未来指针事件的捕获目标。
releasePointerCapture()
此方法释放(停止)之前为特定指针事件设置的捕获目标。
Navigator
扩展
Navigator.maxTouchPoints
属性用于确定在任何单个时间点支持的最大同时触摸点数。
实例
本节包含使用指针事件接口的基本用法实例。
注册事件处理程序
此实例为给定元素的每个事件类型注册一个处理程序。
<html>
<script>
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 gotcapture_handler(event) { }
function lostcapture_handler(event) { }
function init() {
var el=document.getElementById("target");
// 注册指针事件处理程序
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.gotpointercapture = gotcapture_handler;
el.lostpointercapture = lostcapture_handler;
}
</script>
<body onload="init();">
<div id="target"> 点我... </div>
</body>
</html>
事件属性
此实例说明了访问触摸事件的所有属性。
<html>
<script>
var id = -1;
function process_id(event) {
// 根据事件的标识符处理此事件
}
function process_mouse(event) {
// 处理鼠标指针事件
}
function process_pen(event) {
// 处理笔指针事件
}
function process_touch(event) {
// 处理触摸指针事件
}
function process_tilt(tiltX, tiltY) {
// 倾斜处理程序
}
function process_pressure(pressure) {
// 压力处理程序
}
function process_non_primary(event) {
// 非主指针事件处理程序
}
function down_handler(ev) {
// 计算接触点的接触面积
var area = ev.width * ev.height;
// 将缓存的 id 与此事件的 id 进行比较并进行相应处理
if (id == ev.identifier) process_id(ev);
// 调用适当的指针类型处理程序
switch (ev.pointerType) {
case "mouse":
process_mouse(ev);
break;
case "pen":
process_pen(ev);
break;
case "touch":
process_touch(ev);
break;
default:
console.log("不支持指针类型 " + ev.pointerType);
}
// 调用倾斜处理程序
if (ev.tiltX != 0 && ev.tiltY != 0) process_tilt(ev.tiltX, ev.tiltY);
// 调用压力处理程序
process_pressure(ev.pressure);
// 如果此事件不是主指针事件,则调用非主指针事件处理程序
if (!ev.isPrimary) process_non_primary(ev);
}
function init() {
var el=document.getElementById("target");
// 注册 pointerdown 处理程序
el.onpointerdown = down_handler;
}
</script>
<body onload="init();">
<div id="target"> 点我... </div>
</body>
</html>
确定主指针
在某些情况下,可能存在多个指针(例如同时具有触摸屏和鼠标的设备)或指针支持多个接触点(例如支持多个手指触摸的触摸屏)。应用程序可以使用 isPrimary
属性来识别每个指针类型的活动指针集合中的主指针。如果应用程序只想支持主指针,它可以忽略所有非主指针事件。
鼠标只有一个指针,所以它永远是主指针。对于触摸输入,如果用户在没有其他活动触摸时触摸屏幕,则将指针视为主指针。对于钢笔和手写笔输入,如果用户的笔最初接触到屏幕之后,没有其他活动的笔接触到屏幕,则该笔的指针被认为是主指针。
确定按钮状态
一些指针设备,例如鼠标和笔,支持多个按钮,并且按钮可以同时(chorded)被按下,即指针设备上的另一个按钮已经被按下时,再按下一个附加的按钮。
为了确定按钮按下的状态,指针事件使用 MouseEvent
接口(PointerEvent
的父接口)的 button
和 buttons
属性来区分。
下表提供了各种设备按钮状态的 button
和 buttons
的值。
设备按钮状态 | button |
buttons |
---|---|---|
自上次事件以来,按钮和触摸 / 笔接触均未发生变化 | -1 |
— |
鼠标在没有按下按钮的情况下移动,笔在没有按下按钮的情况下悬停时移动 | — | 0 |
鼠标左键、触控接触、笔接触 | 0 |
1 |
鼠标中键 | 1 |
4 |
鼠标右键,笔筒按钮 | 2 |
2 |
X1 (向后) 鼠标 | 3 |
8 |
X2 (向前) 鼠标 | 4 |
16 |
笔橡皮擦按钮 | 5 |
32 |
注意:
button
属性表示按钮状态的变化。但是,与触摸的情况一样,当一个事件发生多个事件时,它们都具有相同的值。
捕获指针
指针捕获允许将特定指针事件
被重新定位到一个特定的元素,而不是在指针的位置进行正常的命中测试。这可用于确保元素继续接收指针事件,即使指针设备的触点从元素移开(例如通过滚动)。
以下实例显示在元素上设置的指针捕获。
<html>
<script>
function downHandler(ev) {
let el = document.getElementById("target");
// 元素 “target” 将接收 / 捕获更多事件
el.setPointerCapture(ev.pointerId);
}
function init() {
let el = document.getElementById("target");
el.onpointerdown = downHandler;
}
</script>
<body onload="init();">
<div id="target"> 点击我 ... </div>
</body>
</html>
下面的例子显示了一个指针捕获被释放(当一个 pointercancel
事件发生时)。当一个 pointerup
或 pointercancel
事件发生时,浏览器会自动执行释放。
<html>
<script>
function downHandler(ev) {
let el = document.getElementById("target");
// 元素 “target” 将接收 / 捕获更多事件
el.setPointerCapture(ev.pointerId);
}
function cancelHandler(ev) {
let el = document.getElementById("target");
// 释放捕获的指针
el.releasePointerCapture(ev.pointerId);
}
function init() {
let el = document.getElementById("target");
// 注册 `pointerdown` 和 `pointercancel` 处理程序
el.onpointerdown = downHandler;
el.onpointercancel = cancelHandler;
}
</script>
<body onload="init();">
<div id="target"> 点击我 ... </div>
</body>
</html>
touch-action
CSS 属性
touch-action
CSS 属性用于指定浏览器是否应将其默认(原生)触摸行为(例如缩放或平移)应用于区域。此属性可应用于所有元素,除了:非替换内联元素、表格行、行组、表格列和列组。
auto
的值意味着浏览器可以自由地应用其默认触摸行为(到指定区域),而 none
值禁用浏览器对该区域的默认触摸行为。pan-x
和 pan-y
值表示从指定区域开始的触摸分别仅用于水平和垂直滚动。manipulation
值意味着浏览器可能认为从元素开始的触摸只是为了滚动和缩放。
<html>
<body>
<div style="touch-action:none;">不能触摸这里... </div>
</body>
</html>
在以下实例中,某些 button
元素的默认触摸行为被禁用。
button#tiny {
touch-action: none;
}
在下面的例子中,当 target
元素被触摸时,它只会在水平方向平移。
#target {
touch-action: pan-x;
}
与鼠标事件的兼容性
尽管指针事件接口使应用程序能够在支持指针的设备上创建增强的用户体验,但现实是当今绝大多数 Web 内容都设计为只能使用鼠标输入。因此,即使浏览器支持指针事件,浏览器仍必须处理鼠标事件,以便假设仅鼠标输入的内容无需直接修改即可正常工作。理想情况下,启用指针的应用程序不需要显式处理鼠标输入。但是,由于浏览器必须处理鼠标事件,因此可能存在一些需要处理的兼容性问题。本节包含有关指针事件和鼠标事件交互的信息,以及对应用程序开发人员的影响。
浏览器可以将通用指针输入映射到鼠标事件以与基于鼠标的内容兼容。这种事件映射称为兼容性鼠标事件。开发人员可以通过取消 pointerdown
事件来阻止某些兼容性鼠标事件的产生,但请注意:
- 只有在指针向下时才能阻止鼠标事件。
- 悬停指针(例如,没有按下按钮的鼠标)无法阻止其鼠标事件。
mouseover
、mouseout
、mouseenter
和mouseleave
事件永远不会被阻止(即使指针向下)。
最佳实践
以下是使用指针事件时要考虑的一些最佳实践:
- 最大限度地减少在事件处理程序中执行的工作量。
- 将事件处理程序添加到特定的目标元素(而不是整个文档或文档树中较高层级的节点)。
- 目标元素(节点)应该足够大以容纳最大的接触表面积(通常是手指触摸)。如果目标区域太小,触摸它可能会导致相邻元素触发其他事件。
规范
规范 |
---|
Pointer Events |
桌面浏览器兼容性
特性 | Chrome | Edge | Firefox | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|---|
基础支持 | 55 | 12 | 59 41 | 11 | 42 | 13 |
PointerEvent() 构造函数 | 55 | 12 | 59 41 | 11 | 42 | 13 |
getCoalescedEvents | 58 | 79 | 59 | 不支持 | 45 | 不支持 |
height | 55 | 12 | 59 41 | 11 102 | 42 | 13 |
isPrimary | 55 | 12 | 59 41 | 10 | 42 | 13 |
pointerId | 55 | 12 | 59 41 | 10 | 42 | 13 |
pointerType | 55 | 12 | 59 41 | 11 103 | 42 | 13 |
pressure | 55 | 12 | 59 41 | 11 104 | 42 | 13 |
tangentialPressure | 58 | 79 | 59 54 | 不支持 | 45 | 13 |
tiltX | 55 | 12 | 59 41 | 10 | 42 | 13 |
tiltY | 55 | 12 | 59 41 | 10 | 42 | 13 |
twist | 58 | 18 | 59 54 | 不支持 | 45 | 13 |
width | 55 | 12 | 59 41 | 11 102 | 42 | 13 |
移动浏览器兼容性
特性 | Android | Chrome for Android | Edge mobile | Firefox for Android | IE mobile | Opera Android | iOS Safari |
---|---|---|---|---|---|---|---|
基础支持 | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
PointerEvent() 构造函数 | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
getCoalescedEvents | 58 | 58 | 未知 | 59 | 未知 | 43 | 不支持 |
height | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
isPrimary | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
pointerId | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
pointerType | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
pressure | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
tangentialPressure | 58 | 58 | 未知 | 54 | 未知 | 43 | 13 |
tiltX | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
tiltY | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
twist | 58 | 58 | 未知 | 54 | 未知 | 43 | 13 |
width | 55 | 55 | 未知 | 41 | 未知 | 42 | 13 |
1. 请参阅 MSDN Pointer 事件更新。
2. 返回以屏幕像素而不是 CSS 文档像素为单位的值。
3. 返回整数枚举而不是字符串。
4. 在不支持压力的硬件上返回 0 而不是 0.5。
作为 Pointer Events – Level 3 规范的一部分,已经为 css touch-action
属性定义了一些新值,但目前这些新值的实现支持有限。