Copyright © 2025 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本规范中的功能扩展或修改了“Pointer Events”(W3C 推荐规范)中用于处理来自鼠标、触控笔或触摸屏等设备的、与硬件无关的指针输入的事件及相关接口。为与现有基于鼠标的内容兼容,本规范还描述了将其他指针设备类型映射为触发 Mouse Events 的方式。
本节描述本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订可在 W3C 标准与草案索引 中找到。
本规范是对 [PointerEvents3] 的更新。其包含编辑性澄清以及支持更多用例的新特性。
本文档由 Pointer Events Working Group 按照 Recommendation track 作为工作草案发布。
作为工作草案发布并不意味着得到 W3C 及其成员的认可。
本文档为草案,可能在任何时间被更新、替换或被其他文档废弃。除作为一项在研工作外,不宜将本文档作为其他用途引用。
本文档由遵循 W3C 专利政策(Patent Policy) 运作的小组制作。 W3C 维护了与本小组可交付成果相关的 专利披露公开列表; 该页面也包含披露专利的说明。任何个人若确知某项专利且认为该专利包含 必要声明(Essential Claim), 必须依据 《W3C 专利政策》第 6 节 披露相关信息。
本节为非规范性内容。
如今,大多数 [HTML] 内容是与鼠标输入配合使用和/或为其设计的。以自定义方式处理输入的内容通常会针对 [UIEVENTS] 鼠标事件进行编码。然而,如今较新的计算设备引入了其他形式的输入,包括触摸屏和笔输入。已经有人提出为这些输入形式各自定义事件类型来处理它们。然而,这种方法在为一种新的输入类型添加支持时,往往会带来不必要的逻辑重复和事件处理开销。当内容仅以某一种设备类型为前提进行编写时,这也经常带来兼容性问题。此外,为了与现有的基于鼠标的内容兼容,大多数用户代理会为所有输入类型触发鼠标事件。这使得很难判断一个鼠标事件究竟表示真实的鼠标设备,还是为了兼容性由其他输入类型生成的,从而难以同时针对两种设备类型编写代码。
为降低同时支持多种输入类型的开发成本,并帮助缓解上述有关鼠标事件的歧义性,本规范定义了一种更抽象的输入形式,称为指针。指针可以是屏幕上的任何接触点,由鼠标光标、手写笔、触摸(包括多点触控)或其他指向型输入设备产生。该模型使得无论用户拥有何种硬件,都更易于编写可良好运行的网站和应用。对于需要设备特定处理的场景,本规范也定义了用于检查产生事件的设备类型的属性。其主要目标是提供一组统一的事件与接口,使跨设备的指针输入更易于编写,同时在需要增强体验时仍允许仅在必要时进行设备特定的处理。
另一个关键目标是,使多线程的用户代理能够在不阻塞脚本执行的情况下,处理用于平移和缩放的直接操控动作(例如在触摸屏上使用手指或手写笔)。
用于处理通用指针输入的事件与鼠标事件非常相似:pointerdown、
pointermove、pointerup、pointerover、pointerout
等。这有助于将内容从鼠标事件轻松迁移到指针事件。
指针事件在提供鼠标事件中常见属性(包括客户端坐标、目标元素、按钮状态)的同时,还新增了适用于其他输入形式的属性,如压力、接触几何尺寸、倾斜角等。作者可以方便地针对指针事件编写代码,在合理的地方在不同输入类型之间共享逻辑,并仅在需要获得最佳体验时,对特定输入类型进行定制化处理。
虽然指针事件来自多种输入设备,但它们并未被定义为由其他设备特定事件生成。兼容性方面这是可行并被鼓励的,但本规范并不要求必须支持其他设备特定事件(如鼠标事件或触摸事件)。用户代理可以仅支持指针事件而不支持任何其他设备事件。为兼容基于鼠标特定事件编写的内容,本规范确实提供了一个可选部分,描述如何基于来自非鼠标设备的指针输入生成兼容鼠标事件。
本规范不就同时支持触摸事件(参见 [TOUCH-EVENTS])和指针事件的用户代理的预期行为提供任何建议。关于这两份规范之间关系的更多信息,请参阅 触摸事件社区小组。
除标记为非规范性的章节外,本规范中的所有编写指南、图示、示例和注记均为非规范性内容。本规范中的其他所有内容均为规范性内容。
本文档中的关键词 MAY、MUST、MUST NOT、OPTIONAL 和 SHOULD 的解释如 BCP 14 [RFC2119] [RFC8174] 所述,并且当且仅当这些词以此处所示的全大写形式出现时,才按该解释进行理解。
本节为非规范性内容。
以下是一些基础示例,展示了作者可能如何使用本规范中的部分 API。更多、更具体的示例见本文档相关章节。
/* 绑定到指针事件或传统的触摸/鼠标 */
if (window.PointerEvent) {
// 如果支持指针事件,只监听指针事件
target.addEventListener("pointerdown", function(e) {
// 如有需要,基于 e.pointerType 应用不同的逻辑
// 以应对触摸/笔/鼠标的不同行为
...
});
...
} else {
// 传统的触摸/鼠标事件处理器
target.addEventListener('touchstart', function(e) {
// 阻止兼容鼠标事件和 click
e.preventDefault();
...
});
...
target.addEventListener('mousedown', ...);
...
}
// 用于键盘处理的附加事件监听器
...
window.addEventListener("pointerdown", detectInputType);
function detectInputType(event) {
switch(event.pointerType) {
case "mouse":
/* 检测到鼠标输入 */
break;
case "pen":
/* 检测到手写笔输入 */
break;
case "touch":
/* 检测到触摸输入 */
break;
default:
/* pointerType 为空(无法检测)或特定于 UA 的自定义类型 */
}
}
<div style="position:absolute; top:0px; left:0px; width:100px;height:100px;"></div>
<script>
window.addEventListener("pointerdown", checkPointerSize);
function checkPointerSize(event) {
event.target.style.width = event.width + "px";
event.target.style.height = event.height + "px";
}
</script>
const event1 = new PointerEvent("pointerover",
{ bubbles: true,
cancelable: true,
composed: true,
pointerId: 42,
pointerType: "pen",
clientX: 300,
clientY: 500
});
eventTarget.dispatchEvent(event1);
let pointerEventInitDict =
{
bubbles: true,
cancelable: true,
composed: true,
pointerId: 42,
pointerType: "pen",
clientX: 300,
clientY: 500,
};
const p1 = new PointerEvent("pointermove", pointerEventInitDict);
pointerEventInitDict.clientX += 10;
const p2 = new PointerEvent("pointermove", pointerEventInitDict);
pointerEventInitDict.coalescedEvents = [p1, p2];
const event2 = new PointerEvent("pointermove", pointerEventInitDict);
eventTarget.dispatchEvent(event2);
<div style="position:absolute; top:0px; left:0px; width:100px;height:100px;"></div>
<script>
window.addEventListener("pointerdown", assignPenColor);
window.addEventListener("pointermove", assignPenColor);
const colorMap = new Map();
function assignPenColor(event) {
const uniqueId = event.persistentDeviceId;
// 检查是否存在唯一 Id。
if (uniqueId == 0) {
return;
}
// 检查是否已为该设备分配颜色。
if (map.has(uniqueId)) {
return;
}
// 为设备分配一种颜色。
let newColor = getNewColor();
map.set(uniqueId, newColor);
return newColor;
}
function getNewColor() {
/* 返回某个颜色值 */
}
</script>
WebIDLdictionary PointerEventInit : MouseEventInit {
long pointerId = 0;
double width = 1;
double height = 1;
float pressure = 0;
float tangentialPressure = 0;
long tiltX;
long tiltY;
long twist = 0;
double altitudeAngle;
double azimuthAngle;
DOMString pointerType = "";
boolean isPrimary = false;
long persistentDeviceId = 0;
sequence<PointerEvent> coalescedEvents = [];
sequence<PointerEvent> predictedEvents = [];
};
[Exposed=Window]
interface PointerEvent : MouseEvent {
constructor(DOMString type, optional PointerEventInit eventInitDict = {});
readonly attribute long pointerId;
readonly attribute double width;
readonly attribute double height;
readonly attribute float pressure;
readonly attribute float tangentialPressure;
readonly attribute long tiltX;
readonly attribute long tiltY;
readonly attribute long twist;
readonly attribute double altitudeAngle;
readonly attribute double azimuthAngle;
readonly attribute DOMString pointerType;
readonly attribute boolean isPrimary;
readonly attribute long persistentDeviceId;
[SecureContext] sequence<PointerEvent> getCoalescedEvents();
sequence<PointerEvent> getPredictedEvents();
};
pointerId导致该事件的指针的唯一标识符。用户代理MAY
为主鼠标指针保留一个通用的 pointerId
值
0 或 1。用于表示并非由指点设备生成的事件的 pointerId 值
-1MUST 被保留并使用。对于其他任何指针,用户代理可自由采用不同的策略与方法分配 pointerId
值。然而,活动指针 在顶层浏览上下文(见
[HTML])中必须唯一,且该标识符MUST NOT
受任何其他顶层浏览上下文影响(即,一个顶层浏览上下文不能假定指针在移出该上下文并进入另一个顶层上下文后,其 pointerId
将保持不变)。
用户代理MAY 回收之前已退役的活动指针的 pointerId 值,或者MAY 始终为某一特定指点设备复用相同的 pointerId(例如,在多用户协作应用中唯一识别某个用户的特定笔/触控笔输入)。不过,在后一种情况下,为尽量降低跨页面或域的指纹与跟踪风险,pointerIdMUST 仅在页面/会话生命周期内与该特定指点设备显式关联,并且在该设备于新会话中再次使用时MUST 选择一个新的随机化 pointerId。
pointerId
的选择算法依赖实现。作者不能假定这些值除了“与其他所有活动指针不同的标识符”之外承载任何特定含义。例如,用户代理可能仅按指针变为活动的顺序,从
0 开始为任意活动指针分配编号——但这些值不保证单调递增。由于是否对特定指点设备复用同一 pointerId
取决于各自实现,强烈不建议作者依赖这种行为,而应转而参考 persistentDeviceId。
width指针的接触几何在 X 轴方向的宽度(CSS 像素,参见
[CSS21])。对于给定指针,该值MAY 在每个事件中更新。对于通常不具备接触几何的输入(如传统鼠标),以及硬件未检测到输入的实际几何的情况,用户代理MUST 返回默认值 1。
height指针的接触几何在 Y 轴方向的高度(CSS 像素,参见
[CSS21])。对于给定指针,该值MAY 在每个事件中更新。对于通常不具备接触几何的输入(如传统鼠标),以及硬件未检测到输入的实际几何的情况,用户代理MUST 返回默认值 1。
pressure指针输入的归一化压力,范围 [0,1],其中 0 与 1
分别表示硬件可检测到的最小与最大压力。对于不支持压力的硬件与平台,当处于active
buttons state 时该值MUST 为 0.5,否则为
0。
tangentialPressure指针输入的归一化切向压力(亦称“桶压”),通常由额外的控制器(例如喷笔触控笔上的指轮)设置,范围
[-1,1],其中 0 为控制器的中立位置。注意某些硬件可能仅支持
[0,1] 的正值。对于不支持切向压力的硬件与平台,该值MUST 为
0。
tiltXY-Z 平面与同时包含换能器(例如笔/触控笔)轴与 Y 轴的平面之间的夹角(以度为单位,范围
[-90,90])。正的 tiltX 指向右侧(X 值增大的方向)。tiltX
可与 tiltY 结合使用,以表示相对于数字化仪法线的倾斜。对于不报告倾斜或角度的硬件与平台,该值MUST
为 0。
tiltX。
tiltYX-Z 平面与同时包含换能器(例如笔/触控笔)轴与 X 轴的平面之间的夹角(以度为单位,范围
[-90,90])。正的 tiltY 朝向用户(Y 值增大的方向)。tiltY
可与 tiltX 结合使用,以表示相对于数字化仪法线的倾斜。对于不报告倾斜或角度的硬件与平台,该值MUST
为 0。
tiltY。
twist换能器(例如笔/触控笔)绕自身主轴的顺时针旋转角度(以度为单位,范围 [0,359])。对于不报告旋转的硬件与平台,该值MUST 为 0。
altitudeAngle换能器(例如笔/触控笔)的仰角(弧度),范围 [0,π/2]——其中 0
表示与表面(X-Y 平面)平行,π/2 表示垂直于表面。对于不报告倾斜或角度的硬件与平台,该值MUST 为
π/2。
altitudeAngle 定义的默认值是 π/2,表示换能器垂直于表面。这与
Touch Events - Level 2 规范对
altitudeAngle 属性的定义不同,后者的默认值为 0。
altitudeAngle 为 π/4(相对 X-Y 平面 45°)。azimuthAngle换能器(例如笔/触控笔)的方位角(弧度),范围 [0, 2π]——其中 0
表示换能器笔帽沿 X 值增大的方向(若从正上方观察,指向“3 点钟”)在 X-Y 平面上,顺时针角度逐渐增大(π/2
在“6 点钟”,π 在“9 点钟”,3π/2 在“12
点钟”)。当换能器完全垂直于表面(altitudeAngle
为 π/2)时,该值MUST 为 0。对于不报告倾斜或角度的硬件与平台,该值MUST 为 0。
azimuthAngle 为 π/6(“4 点钟”)。pointerType指示导致该事件的设备类型(如鼠标、笔、触摸)。如果用户代理要为鼠标、笔/触控笔或触摸输入设备触发指针事件,则 pointerType 的取值MUST 符合下表:
| 指针设备类型 | pointerType 值 |
|---|---|
| 鼠标 | mouse |
| 笔 / 触控笔 | pen |
| 触摸接触 | touch |
如果用户代理无法检测设备类型,则该值MUST
为空字符串。若用户代理支持上述列表之外的指针设备类型,pointerType
的取值SHOULD 加供应商前缀,以避免不同设备类型之间名称冲突。后续规范MAY
为其他设备类型提供更多规范化取值。
pointerType 的基本演示。另请注意,开发者应包含某种默认处理,以适配可能实现了自定义
pointerType 值的用户代理,以及 pointerType 为空字符串的情况。
isPrimary指示该指针是否为此指针类型的主指针。
persistentDeviceId指点设备的唯一标识符。如果硬件支持多个指针,则仅当这些指针在会话内可被唯一识别时,指针事件MUST
获取一个 persistentDeviceId。若指针可被唯一识别,分配给该指点设备的
persistentDeviceId 在本会话剩余时间内保持不变。值 0MUST
保留用于表示生成设备无法识别的事件。类似 pointerId,为降低跨页面或域的指纹与跟踪风险,persistentDeviceIdMUST 仅在页面/会话生命周期内与该特定指点设备显式关联,并在该设备于新会话再次使用时MUST 选择新的随机化 persistentDeviceId。
persistentDeviceId。例如,设备可能无法及时向数字化仪报告其硬件 id,从而导致
pointerdown 时尚无 persistentDeviceId。此类情况下,
persistentDeviceId 可能先为 0,随后再变为有效值。
getCoalescedEvents()返回合并事件列表的方法。
getPredictedEvents()返回预测事件列表的方法。
PointerEventInit 字典由 PointerEvent 接口的构造函数使用,提供构造不受信任(合成)指针事件的机制。它继承自
[UIEVENTS] 中定义的 MouseEventInit
字典。参见示例了解如何触发不受信任的指针事件的示例代码。
事件构造步骤用于
PointerEvent:将 PointerEventInit 的 coalescedEvents 克隆到合并事件列表,并将 PointerEventInit 的 predictedEvents 克隆到预测事件列表。
PointerEvent 接口继承自 UI Events 中定义的 MouseEvent。另请注意
CSSOM
View Module 中的拟议扩展,它将各种坐标属性由 long 改为
double,以允许小数坐标。对于已对 PointerEvent 实现该扩展、但尚未对常规 MouseEvent 实现的用户代理,在处理click、
auxclick 与 contextmenu 事件时有额外要求。
在多指针(例如多点触控)场景中,isPrimary 属性用于在每种指针类型的活动指针集合中标识一个主指针。
pointerType
一个)。例如,同时移动触摸接触与鼠标光标会产生两个都被视为主指针的指针。isPrimary 设为
false。
“触发名为
e 的指针事件”指使用 PointerEvent 来触发一个名为
e 的事件,并按PointerEvent 接口与属性与默认操作中的定义设置其属性。
若该事件不是 gotpointercapture、lostpointercapture、click、auxclick
或 contextmenu,则为该 PointerEvent 运行处理挂起的指针捕获步骤。
确定要触发事件的目标如下:
令 targetDocument 为目标的节点文档 [DOM]。
如果事件为 pointerdown、pointermove 或 pointerup,则将该事件的
活动文档设置为 targetDocument。
如果事件为 pointerdown,关联设备为直接操控设备,且目标为 Element,则按照隐式指针捕获的描述,为该 pointerId 对目标元素设置指针捕获。
在触发该事件之前,为了确保事件顺序
[UIEVENTS],用户代理SHOULD
将目标视为指点设备已从
previousTarget 移动到该目标。若 needsOverEvent 标志被设置,则即使目标元素相同也需要一个 pointerover 事件。
向确定的目标触发事件。
将确定的目标保存为该指针的 previousTarget,并将 needsOverEvent 标志重置为
false。若在任意时刻 previousTarget 不再连接[DOM],则沿着向
previousTarget 分派事件对应的事件路径,更新 previousTarget 为最近的仍连接[DOM]的父节点,并将
needsOverEvent 标志设为 true。
本规范中定义的事件类型的 bubbles 与 cancelable 属性及其默认操作如下表所示。有关各事件类型的详细信息,见指针事件类型。
| 事件类型 | 冒泡 | 可取消 | 默认操作 |
|---|---|---|---|
pointerover |
是 | 是 | 无 |
pointerenter |
否 | 否 | 无 |
pointerdown |
是 | 是 | 不定:当指针为主指针时,与 mousedown 事件的所有默认操作相同
取消该事件也会阻止后续兼容鼠标事件的触发。 |
pointermove |
是 | 是 | 不定:当指针为主指针时,与 mousemove 的所有默认操作相同 |
pointerrawupdate |
是 | 否 | 无 |
pointerup |
是 | 是 | 不定:当指针为主指针时,与 mouseup 的所有默认操作相同 |
pointercancel |
是 | 否 | 无 |
pointerout |
是 | 是 | 无 |
pointerleave |
否 | 否 | 无 |
gotpointercapture |
是 | 否 | 无 |
lostpointercapture |
是 | 否 | 无 |
视口操作(平移与缩放)——通常源于直接操控交互——有意不作为指针事件的默认操作,这意味着这些行为(例如在触摸屏上移动手指导致页面平移)不能通过取消指针事件来抑制。作者必须改用
touch-action 显式声明文档区域的直接操控行为。移除对事件取消的依赖有助于用户代理进行性能优化。
对于 pointerenter 与 pointerleave 事件,composed
[DOM] 属性SHOULD 为
false;对于表中其他指针事件,该属性SHOULD 为
true。
对于上表中的所有指针事件,detail
[UIEVENTS] 属性SHOULD 为 0。
fromElement 与
toElement 以支持遗留内容。我们鼓励这些用户代理在 PointerEvents 中将这些(继承的)属性值设为
null,以引导作者改用标准化替代项(target 与 relatedTarget)。
类似 MouseEvent 的 relatedTarget,relatedTarget
应初始化为指针刚离开的元素(对于 pointerover 或
pointerenter 事件),或指针即将进入的元素(对于 pointerout 或 pointerleave 事件)。对于其他指针事件,该值默认为
null。注意,当一个元素获得指针捕获后,该指针的后续所有事件都被视为发生在捕获元素的边界内部。
对于 gotpointercapture 与 lostpointercapture
事件,除上表定义的属性外,其他所有属性应与导致用户代理运行处理挂起的指针捕获步骤并触发
gotpointercapture 与 lostpointercapture 的指针事件相同。
用户代理MUST 在隐式释放指针捕获时,以及在触发非
gotpointercapture 与 lostpointercapture 的指针事件时,运行以下步骤:
lostpointercapture 的指针事件。
gotpointercapture 的指针事件。
如click、auxclick
与 contextmenu 事件一节所述,即使已派发 lostpointercapture 事件,若存在对应的
click、auxclick 或 contextmenu 事件,它们仍会派发到捕获目标。
用户代理MUST 在检测到网页不太可能继续接收具有某个特定 pointerId 的指针事件时,抑制该指针事件流。以下任一情形满足该条件(也MAY 存在其他情形):
touch-action CSS
属性一节。
pointercancel 事件。pointerout 事件。pointerleave 事件。相对于屏幕表面移动或其属性发生变化的指点设备,会触发指针事件类型中定义的各种事件。对于静止的指点设备(既未相对屏幕表面移动,也未发生任何属性变化),当布局变化影响到该指针的命中测试目标时,用户代理MUST
触发某些边界事件,详见 pointerover、pointerenter、pointerout
与 pointerleave。出于性能考虑(例如避免过多的命中测试或由边界事件监听器引起的布局变化),用户代理MAY 延迟触发这些边界事件。
pointermove 事件。Pointer Events 提供两组互补的属性,用于表示换能器相对于 X-Y 平面的朝向:tiltX /
tiltY(在最初的 Pointer Events 规范中引入)以及 azimuthAngle /
altitudeAngle(借鉴自 Touch Events - Level
2 规范)。
根据具体硬件与平台,用户代理通常只会接收到相对于屏幕平面的换能器朝向的一组值——要么是 tiltX /
tiltY,要么是 altitudeAngle / azimuthAngle。用户代理MUST 使用以下算法在两组值之间进行转换。
当用户代理由 azimuthAngle / altitudeAngle 计算 tiltX /
tiltY 时,SHOULD 按照 [ECMASCRIPT] 中 Math.round 的规则对最终整数值进行取整。
/* Converting between tiltX/tiltY and altitudeAngle/azimuthAngle */
function spherical2tilt(altitudeAngle, azimuthAngle) {
const radToDeg = 180/Math.PI;
let tiltXrad = 0;
let tiltYrad = 0;
if (altitudeAngle == 0) {
// the pen is in the X-Y plane
if (azimuthAngle == 0 || azimuthAngle == 2*Math.PI) {
// pen is on positive X axis
tiltXrad = Math.PI/2;
}
if (azimuthAngle == Math.PI/2) {
// pen is on positive Y axis
tiltYrad = Math.PI/2;
}
if (azimuthAngle == Math.PI) {
// pen is on negative X axis
tiltXrad = -Math.PI/2;
}
if (azimuthAngle == 3*Math.PI/2) {
// pen is on negative Y axis
tiltYrad = -Math.PI/2;
}
if (azimuthAngle>0 && azimuthAngle<Math.PI/2) {
tiltXrad = Math.PI/2;
tiltYrad = Math.PI/2;
}
if (azimuthAngle>Math.PI/2 && azimuthAngle<Math.PI) {
tiltXrad = -Math.PI/2;
tiltYrad = Math.PI/2;
}
if (azimuthAngle>Math.PI && azimuthAngle<3*Math.PI/2) {
tiltXrad = -Math.PI/2;
tiltYrad = -Math.PI/2;
}
if (azimuthAngle>3*Math.PI/2 && azimuthAngle<2*Math.PI) {
tiltXrad = Math.PI/2;
tiltYrad = -Math.PI/2;
}
}
if (altitudeAngle != 0) {
const tanAlt = Math.tan(altitudeAngle);
tiltXrad = Math.atan(Math.cos(azimuthAngle) / tanAlt);
tiltYrad = Math.atan(Math.sin(azimuthAngle) / tanAlt);
}
return {"tiltX":tiltXrad*radToDeg, "tiltY":tiltYrad*radToDeg};
}
function tilt2spherical(tiltX, tiltY) {
const tiltXrad = tiltX * Math.PI/180;
const tiltYrad = tiltY * Math.PI/180;
// calculate azimuth angle
let azimuthAngle = 0;
if (tiltX == 0) {
if (tiltY > 0) {
azimuthAngle = Math.PI/2;
}
else if (tiltY < 0) {
azimuthAngle = 3*Math.PI/2;
}
} else if (tiltY == 0) {
if (tiltX < 0) {
azimuthAngle = Math.PI;
}
} else if (Math.abs(tiltX) == 90 || Math.abs(tiltY) == 90) {
// not enough information to calculate azimuth
azimuthAngle = 0;
} else {
// Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90
const tanX = Math.tan(tiltXrad);
const tanY = Math.tan(tiltYrad);
azimuthAngle = Math.atan2(tanY, tanX);
if (azimuthAngle < 0) {
azimuthAngle += 2*Math.PI;
}
}
// calculate altitude angle
let altitudeAngle = 0;
if (Math.abs(tiltX) == 90 || Math.abs(tiltY) == 90) {
altitudeAngle = 0
} else if (tiltX == 0) {
altitudeAngle = Math.PI/2 - Math.abs(tiltYrad);
} else if (tiltY == 0) {
altitudeAngle = Math.PI/2 - Math.abs(tiltXrad);
} else {
// Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90
altitudeAngle = Math.atan(1.0/Math.sqrt(Math.pow(Math.tan(tiltXrad),2) + Math.pow(Math.tan(tiltYrad),2)));
}
return {"altitudeAngle":altitudeAngle, "azimuthAngle":azimuthAngle};
}
下文列出了本规范中定义的事件类型。
对于主指针,这些事件(除 gotpointercapture 与 lostpointercapture 外)也可能触发兼容鼠标事件。
当出现以下任一情况时,用户代理MUST 触发一个名为 pointerover 的指针事件:
pointerdown 事件之前(参见 pointerdown)。当出现以下任一情况时,用户代理MUST 触发一个名为 pointerenter 的指针事件:
pointerdown 事件之前(参见 pointerdown)。当指针进入 活动按钮状态 时,用户代理MUST 触发一个名为 pointerdown 的指针事件。鼠标:从无按钮按下转为至少一个按钮按下。触摸:与 数字化仪 产生物理接触。笔:在未按任意按钮时与数字化仪接触,或在悬停时从无按钮按下转为至少一个按钮按下。
对于不支持悬停的输入设备,用户代理MUST 在派发 pointerdown 事件之前,按顺序触发名为 pointerover 与 pointerenter 的指针事件。
pointerdown 事件(如果
isPrimary 为 true)来阻止某些兼容鼠标事件的触发。这会在该指针上设置
PREVENT MOUSE EVENT 标志。但这不会阻止
mouseover、mouseenter、mouseout 或
mouseleave 事件的触发。
当指针发生任何不会触发 pointerdown 或 pointerup
事件的属性变化时,用户代理MUST 触发一个名为 pointermove
的指针事件。包括坐标、压力、切向压力、倾斜、旋转、接触几何(width 与
height)或 复合按键 的变化。
用户代理MAY 延迟派发 pointermove(例如出于性能原因)。延迟期间的
合并事件 信息会通过单一派发的 pointermove 的 getCoalescedEvents
方法暴露。事件最终坐标用于确定事件目标。
当指针发生任何不会触发 pointerdown 或 pointerup 的属性变化时,并且仅在安全上下文内,用户代理MUST 触发一个名为 pointerrawupdate 的指针事件。相关属性列表参见
pointermove。
与 pointermove 相反,用户代理SHOULD 尽快且尽可能频繁派发 pointerrawupdate,频率取决于
JavaScript 能否处理。
由于 pointermove 事件可能被延迟或合并,pointerrawupdate 的 target
可能与 pointermove 不同,因为用于确定目标的最终位置可能与其合并事件不同。
注意:如果已经存在另一个具有相同 pointerId 且尚未在事件循环中派发的 pointerrawupdate,用户代理MAY 将新的 pointerrawupdate 合并到该事件中,而不是创建新任务。这可能导致 pointerrawupdate
具有合并事件,并在事件循环中处理时一次性作为该事件的合并事件交付。详见 getCoalescedEvents。
关于 pointerrawupdate 与 pointermove
的排序:如果用户代理收到的平台更新同时导致 pointerrawupdate 与 pointermove,则用户代理MUST 先派发
pointerrawupdate,再派发对应的 pointermove。
除 target 外,自上次 pointermove 事件以来所有已派发 pointerrawupdate 的合并事件列表拼接结果,与下一次 pointermove 的合并事件在其他属性上等价。pointerrawupdate 的大多数属性与 pointermove 相同,唯一不同是
cancelable 对于 pointerrawupdateMUST 为 false。
用户代理SHOULD 不为 pointerrawupdate 触发兼容鼠标事件。
pointerrawupdate
添加监听器可能对页面性能产生负面影响,具体取决于用户代理实现。大多数用例下其它指针事件类型已足够。仅当需要高频事件且能快速处理时才应添加 pointerrawupdate
监听器,这些情况下通常无需监听其他指针事件类型。当指针离开 活动按钮状态 时,用户代理MUST 触发一个名为 pointerup
的指针事件。鼠标:从至少一个按钮按下转为无按钮按下。触摸:从 数字化仪 移除物理接触。笔:在无按钮按下情况下移除与数字化仪的物理接触,或在悬停时从至少一个按钮按下转为无按钮按下。
对于不支持悬停的输入设备,用户代理MUST 在派发 pointerup 之后,依次触发名为 pointerout 与 pointerleave 的指针事件。
所有 pointerup 事件的 pressure 值均为
0。
如果指针当前被捕获,用户代理MUST 还要 隐式释放指针捕获。
当检测到需要抑制指针事件流的情形时,用户代理MUST 触发一个名为 pointercancel 的指针事件。
pointercancel 事件中以下属性的值MUST 与最后一个具有相同 pointerId
的已派发指针事件一致:
width、height、pressure、tangentialPressure、tiltX、tiltY、twist、altitudeAngle、azimuthAngle、pointerType、isPrimary
以及从
[UIEVENTS] 继承的坐标。pointercancel 事件中的
coalescedEvents 与 predictedEvents 列表MUST 为空,事件的
cancelableMUST 为 false。
当出现以下任一情况时,用户代理MUST 触发一个名为 pointerout 的指针事件:
当出现以下任一情况时,用户代理MUST 触发一个名为 pointerleave 的指针事件:
pointerup 之后(参见 pointerup)。当某元素获得指针捕获时,用户代理MUST 触发一个名为 gotpointercapture
的指针事件。该事件在获得指针捕获的元素上触发。此后该指针的事件将派发到此元素。参见 设置指针捕获 与 处理挂起的指针捕获 部分。
当指针捕获被释放后,用户代理MUST 触发一个名为 lostpointercapture 的指针事件。此事件MUST 在释放捕获后、该指针的任何后续事件之前触发。事件在移除捕获的元素上触发。该指针后续事件(除 click、
auxclick、contextmenu 外)通过常规命中测试确定目标。参见 释放指针捕获、隐式释放指针捕获 与 处理挂起的指针捕获 部分。
下节描述对现有 Element 接口的扩展,以便于设置和释放指针捕获。
WebIDLpartial interface Element {
undefined setPointerCapture (long pointerId);
undefined releasePointerCapture (long pointerId);
boolean hasPointerCapture (long pointerId);
};
setPointerCapture()为由参数 pointerId 标识的指针在调用此方法的元素上设置指针捕获。对于该指针的后续事件,捕获目标会替代正常的命中测试结果,仿佛指针始终位于捕获目标之上,且这些事件MUST 在捕获释放前始终定向到此元素。为使此方法生效,指针MUST 处于其 活动按钮状态,否则将静默失败。当提供的参数不匹配任何 活动指针 时,抛出“NotFoundError” DOMException。
releasePointerCapture()为由参数 pointerId 标识的指针,从调用此方法的元素上释放指针捕获。该指针的后续事件将按照正常的命中测试机制(不在本规范范围内)确定事件目标。当提供的参数不匹配任何
活动指针 时,抛出“NotFoundError” DOMException。
hasPointerCapture指示调用此方法的元素是否对由参数 pointerId 标识的指针拥有指针捕获。具体而言,若 挂起的指针捕获目标覆盖 对应的 pointerId 被设置为调用此方法的元素,则返回
true,否则返回 false。
setPointerCapture()
之后,该方法会立即返回 true,即便该元素尚未收到 gotpointercapture 事件。因此,它可用于在 隐式指针捕获 发生时(例如在 pointerdown 事件监听器内部)进行检测。下节描述对现有 GlobalEventHandlers
mixin 的扩展,以便于事件处理程序的注册。
WebIDLpartial interface mixin GlobalEventHandlers {
attribute EventHandler onpointerover;
attribute EventHandler onpointerenter;
attribute EventHandler onpointerdown;
attribute EventHandler onpointermove;
[SecureContext] attribute EventHandler onpointerrawupdate;
attribute EventHandler onpointerup;
attribute EventHandler onpointercancel;
attribute EventHandler onpointerout;
attribute EventHandler onpointerleave;
attribute EventHandler ongotpointercapture;
attribute EventHandler onlostpointercapture;
};
onpointeroverpointerover 事件类型。
onpointerenterpointerenter 事件类型。
onpointerdownpointerdown 事件类型。
onpointermovepointermove 事件类型。
onpointerrawupdatepointerrawupdate 事件类型。
onpointeruppointerup 事件类型。
onpointercancelpointercancel 事件类型。
onpointeroutpointerout 事件类型。
onpointerleavepointerleave 事件类型。
ongotpointercapturegotpointercapture
事件类型。
onlostpointercapturelostpointercapture
事件类型。
如 属性与默认操作 中所述,视口操控(平移与缩放)不能通过取消指针事件来抑制。作者必须使用
touch-action CSS 属性以声明性方式定义要允许/要抑制的行为。
touch-action CSS 属性看起来仅与触摸输入有关,但它实际上适用于所有允许通过 直接操控
来平移和缩放的指针输入形式。
| 名称: | touch-action |
|---|---|
| 取值: | auto | none | [ [ pan-x | pan-left |
pan-right ] || [ pan-y | pan-up |
pan-down ] ] | manipulation
|
| 初始值: | auto |
| 应用于: | 除非替换的行内元素、表格行、行组、表列和列组外的所有元素 |
| 是否继承: | 否 |
| 百分比: | 不适用 |
| 媒体: | 视觉 |
| 计算值: | 与指定值相同 |
| 规范顺序: | 按语法 |
| 动画类型: | 不可动画 |
touch-action CSS 属性决定 直接操控 交互(尽管属性名包含“touch”,但并不限于触摸)MAY 是否触发用户代理的平移和缩放行为。参见
touch-action 的取值 一节。
在开始平移或缩放之前,如果以下条件全部满足,用户代理MUST 抑制一个指针事件流:
pointerdown 事件,且pointerdown 之后的 pointerup 或 pointercancel 事件。touch-action 不会应用/级联到嵌入式浏览上下文。例如,即使在 <iframe> 上应用
touch-action,也不会对该 <iframe> 内部的平移和缩放直接操控行为产生任何影响。
当用户使用 直接操控 指针(如触摸屏上的手指或触控笔)与元素交互时,该输入的效果由
touch-action 属性的值,以及该元素及其祖先的默认直接操控行为共同决定,如下所示:
touch-action。注意,如果应用了 CSS 变换,则元素的坐标空间可能与屏幕坐标不同,从而影响此处的“符合”判断;例如,相对屏幕旋转 90
度的元素,其 X 轴将与屏幕坐标的 Y 轴平行。touch-action 属性,则该平移交互受支持。
document 元素之间每个元素的
touch-action 属性,则该缩放交互受支持。
touch-action 值的任何更改都将被忽略。例如,在 pointerdown 处理脚本中将某元素的
touch-action 从 auto 程序化修改为
none,并不会导致用户代理在该指针保持活动期间中止或抑制与该输入相关的任何平移或缩放行为。
touch-action 的 pan-*
取值,一旦用户代理在手势开始时决定是否直接处理该手势,则在该指针保持活动期间,对同一手势方向的后续变化用户代理SHOULD 忽略。例如,若某元素设置为
touch-action: pan-y(意味着只有垂直平移由用户代理处理),而触摸手势最初是水平的,即使用户在手指仍触摸屏幕时将手势方向改为垂直,也不应发生垂直平移。
touch-action 取值不在本规范范围内。
touch-action 属性涵盖与视口平移和缩放相关的直接操控行为。任何额外的用户代理行为(如文本选择/高亮、激活链接和表单控件)MUST
NOT 受该 CSS 属性影响。
auto 或 none 取值行为的定义,不在本规范范围内。
pan-x 或 pan-y),在平移过程中不可改变该轴。
touch-action 取值在 [COMPAT] 中定义。方向特定的 pan 取值对于自定义某些回弹(overscroll)行为很有用。例如,为实现简单的“下拉刷新”,当滚动位置为
0 时可将文档的 touch-action 设为 pan-x pan-down,否则设为
pan-x pan-y。这样可允许指针事件处理程序定义从文档顶部开始的向上平移/滚动的行为。
方向特定的 pan 取值也可用于组合一个组件:该组件在原生滚动的元素中用指针事件处理实现自定义平移(或反之)。例如,一个图片轮播可使用
pan-y,以确保其在任何水平平移操作中接收指针事件,同时不干扰文档的垂直平移。当轮播达到最右端时,它可以将其
touch-action 更改为
pan-y pan-right,以便在可能的情况下,超出其范围的后续滚动操作可以在视口内滚动文档。正在进行的平移/滚动操作无法在进行过程中更改其行为。
auto,用户代理通常在 click 之前添加 300ms 延迟,以便处理双击手势。在这些情况下,显式设置
touch-action: none 或 touch-action: manipulation
将移除此延迟。注意,用于判定单击或双击手势的方法不在本规范范围内。
<div style="touch-action: none;">
该元素会接收所有本会导致平移或缩放的直接操控交互的指针事件。
</div>
<div style="touch-action: pan-x;">
当未进行水平方向的平移时,该元素会接收指针事件。
</div>
<div style="overflow: auto;">
<div style="touch-action: none;">
该元素会接收所有本会导致平移或缩放的直接操控交互的指针事件。
</div>
<div>
在此元素上的直接操控交互 <MAY> 被用于操控父元素。
</div>
</div>
<div style="overflow: auto;">
<div style="touch-action: pan-y;">
<div style="touch-action: pan-x;">
该元素会接收所有直接操控交互的指针事件,因为
它仅允许水平平移,而位于其与可滚动元素之间的
某个中间祖先仅允许垂直平移。
因此,用户代理不会处理任何平移/缩放的直接操控行为。
</div>
</div>
</div>
<div style="overflow: auto;">
<div style="touch-action: pan-y pan-left;">
<div style="touch-action: pan-x;">
当不向左平移时,该元素会接收指针事件。
</div>
</div>
</div>
本节为非规范性内容。
指针捕获允许某一特定指针的事件(包括任何兼容鼠标事件)被重定向到一个特定元素,而不是基于该指针位置的正常命中测试结果。它在自定义滑块控件场景中很有用(例如类似于 [HTML] 的
<input type="range"> 控件)。可以在滑块的滑块拇指元素上设置指针捕获,使用户即便让指针滑出拇指区域也能继续来回拖动。
pointerdown
之后,即便指针偏离拇指,仍可利用指针捕获继续拖动拇指。通过调用 element.setPointerCapture(pointerId) 方法在一个类型为 element 的 Element 上设置指针捕获。
当该方法被调用时,用户代理MUST 依次执行以下步骤:
pointerId 不匹配任何活动指针,则抛出“NotFoundError” DOMException。pointerId 指定的活动指针。
InvalidStateError” DOMException。pointerLockElement)时被调用,则抛出“InvalidStateError” DOMException。
pointerId,将挂起的指针捕获目标覆盖 (pending pointer capture target override)设置为调用该方法的 Element。pointerdown 监听器中尝试释放失败的情形。
通过调用 element.releasePointerCapture(pointerId) 方法在元素上显式释放指针捕获。调用该方法时,用户代理MUST 执行以下步骤:
pointerId 不匹配任何活动指针,且这些步骤的触发并非由于隐式释放指针捕获,则抛出“NotFoundError” DOMException。pointerId 的 hasPointerCapture 为 false,则终止这些步骤。
pointerId,清除(若已设置)挂起的指针捕获目标覆盖。实现用于平移与缩放的直接操控交互的输入(例如触摸或触控笔)SHOULD
表现得仿佛在任何 pointerdown 监听器被调用前刚对目标元素调用了 setPointerCapture。可以使用 hasPointerCapture API(例如在 pointerdown 监听器中)来判断此行为是否发生。如果在下一次指针事件触发前未调用 releasePointerCapture,则会向该目标正常派发
gotpointercapture 事件表示捕获已激活。
在触发 pointerup 或 pointercancel 事件之后,用户代理MUST 清除刚被派发的 pointerup
或 pointercancel 事件所对应的 pointerId 的 挂起的指针捕获目标覆盖,然后执行 处理挂起指针捕获 步骤以在必要时触发 lostpointercapture。
在执行完 处理挂起指针捕获 步骤后,如果该指针支持悬停,用户代理MUST 也要发送对应的边界事件以反映无捕获状态下指针当前位置。
当 指针捕获目标覆盖 (pointer capture target override) 不再连接 (connected) [DOM] 时,该 指针捕获目标覆盖SHOULD 设置为 document。
当 挂起的指针捕获目标覆盖 不再连接 (connected) [DOM] 时, 该 挂起的指针捕获目标覆盖 节点SHOULD 被清除。
lostpointercapture
事件。
当一个指针锁 [PointerLock] 成功应用于某元素时,如果任何元素已被设置为捕获或处于待捕获状态,用户代理MUST 按照调用 releasePointerCapture 方法的步骤执行。
出于性能考虑,用户代理可能选择不在每次指针的某个可测属性
(如坐标、压力、切向压力、倾斜、旋转或接触几何)更新时都发送一个 pointermove 事件。取而代之,它们可能将多个变化合并到单个 pointermove 或 pointerrawupdate 事件中。虽然此方式减少了用户代理MUST 进行的事件处理次数,但会自然降低指针位置跟踪的粒度与精确度,特别是快速且幅度大的移动。通过 getCoalescedEvents
方法,应用可以访问原始未合并的位置信息,从而更精细地处理指针运动数据。例如在绘图应用中,未合并事件可用于绘制更平滑的曲线,使其更接近指针真实移动轨迹。
pointermove
事件的合并坐标(灰点)时,曲线明显呈现折角与锯齿;使用 getCoalescedEvents() 提供的更细粒度点(红圈)可获得更平滑的轨迹。
PointerEvent 具有关联的 合并事件列表
(coalesced
events list)(零个或多个 PointerEvent)。对于可信的 pointermove 与 pointerrawupdate 事件,该列表是所有被合并进此事件的
PointerEvent 序列。可信的“父” pointermove 与 pointerrawupdate
事件表示这些合并事件的累计,且可能包含额外处理(例如对齐显示刷新率)。因此这些事件的合并事件列表总是至少包含一个事件。对所有其他可信事件类型,该列表为空。非可信事件的 合并事件列表初始化为构造函数传入的值。
可信事件的合并事件列表中的事件具有以下特征:
timeStamp 值 [DOM]——所有合并事件的 timeStamp 小于或等于调用 getPredictedEvents
方法的已派发指针事件的时间戳。合并事件列表MUST 按时间戳排序,首个事件拥有最小 timeStamp。pointerId、pointerType 和
isPrimary。
<style>
/* 禁用固有的用户代理直接操控行为(如平移或缩放),
让画布元素上的所有事件都交由应用处理。 */
canvas { touch-action: none; }
</style>
<canvas id="drawSurface" width="500px" height="500px" style="border:1px solid black;"></canvas>
<script>
const canvas = document.getElementById("drawSurface"),
context = canvas.getContext("2d");
canvas.addEventListener("pointermove", (e)=> {
if (e.getCoalescedEvents) {
for (let coalesced_event of e.getCoalescedEvents()) {
paint(coalesced_event); // 绘制所有原始/未合并点
}
} else {
paint(e); // 绘制最终的合并点
}
});
function paint(event) {
if (event.buttons>0) {
context.fillRect(event.clientX, event.clientY, 5, 5);
}
}
</script>
所有已派发事件的顺序MUST 与原始事件真实顺序一致。
例如如果一个 pointerdown 事件导致合并的 pointermove 事件进行派发,用户代理MUST 先派发一个包含该 pointerId 所有合并事件的 pointermove,然后再派发 pointerdown。
下表示例展示了实际发生的事件(时间戳递增)与用户代理派发的事件:
| 实际事件 | 派发事件 |
|---|---|
指针 (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2),含 1
个合并事件 |
指针 (pointerId=1)
坐标变化 |
pointerrawupdate (pointerId=1),含 1
个合并事件 |
指针 (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2),含 1
个合并事件 |
指针 (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2),含 1
个合并事件 |
指针 (pointerId=1)
坐标变化 |
pointerrawupdate (pointerId=1),含 1
个合并事件 |
指针 (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2),含 1
个合并事件 |
指针 (pointerId=1) 按钮按下
|
pointermove (pointerId=1),含 2
个合并事件pointermove (pointerId=2),含 4
个合并事件pointerdown (pointerId=1),含 0
个合并事件 |
指针 (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2),含 1
个合并事件 |
指针 (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2),含 1
个合并事件 |
指针 (pointerId=1) 按钮释放
|
pointermove (pointerId=2),含 2
个合并事件pointerup (pointerId=1),含 0
个合并事件 |
一些用户代理具有内置算法,可在连续确认的指针移动之后基于当前手势的先前事件及其速度/轨迹对未来指针移动位置进行预测。应用可以使用 getPredictedEvents
方法获取这些信息,以“提前绘制”到预测位置,从而降低感知延迟,并在实际点到达后舍弃这些预测点。
pointermove
事件的合并坐标,并显示用户代理预测的未来点(灰圈)。PointerEvent 具有关联的 预测事件列表
(predicted
events list)(零个或多个 PointerEvent)。对于可信的 pointermove 事件,它是用户代理预测将要跟随该事件的
PointerEvent 序列。其它可信事件类型为空。非可信事件的 预测事件列表 初始化为构造函数传入的值。
列表中事件的数量以及它们距离当前时间戳的远近由用户代理及其预测算法决定。
可信事件的预测事件列表中的事件具有以下特征:
timeStamp 值 [DOM]——所有预测事件的 timeStamp 大于或等于调用 getPredictedEvents
的已派发指针事件时间戳。预测事件列表MUST 按时间戳排序,首个事件具有最小时间戳。pointerId、pointerType 与
isPrimary。
作者应仅在下一个指针事件派发前视预测事件为有效预测。取决于用户代理预测的未来长度,常规指针事件可能在某些预测事件时间戳之前更早派发。
let predicted_points = [];
window.addEventListener("pointermove", function(event) {
// 清除之前绘制的预测点。
for (let e of predicted_points.reverse()) {
clearPoint(e.pageX, e.pageY);
}
// 绘制自上次事件以来实际发生的移动。
for (let e of event.getCoalescedEvents()) {
drawPoint(e.pageX, e.pageY);
}
// 绘制当前预测点以减少感知延迟。
predicted_points = event.getPredictedEvents();
for (let e of predicted_points) {
drawPoint(e.pageX, e.pageY);
}
});
当一个可信的 PointerEvent 被创建时,用户代理SHOULD 对其 合并事件列表 和 预测事件列表中的每个事件执行以下步骤:
pointerId、
pointerType、
isPrimary 以及 isTrusted 设为与“父”指针事件相同。
cancelable 与 bubbles 设为
false(这些事件永远不会单独派发)。PointerEvent 默认值。当一个可信的 PointerEvent 的 target 被更改时,用户代理SHOULD 针对其 合并事件列表 和 预测事件列表中的每个事件:
当今绝大多数现存网页内容只编写了 Mouse Events。下文描述一个算法,用户代理 MAY 将通用指针输入映射为鼠标事件,以与这些内容保持兼容。
与鼠标事件的兼容映射是本规范的一个 OPTIONAL 特性。鼓励用户代理支持该特性以获得与现有遗留内容的最佳兼容性。
总体上,兼容鼠标事件旨在与其对应的指针事件“交错”出现。不过这一具体顺序并非强制,支持兼容鼠标事件的用户代理 MAY 延迟或成组派发鼠标事件,只要它们的相对顺序保持一致。
特别是在触摸屏输入的情况下,用户代理 MAY 为手势识别应用额外启发式(除非作者通过 明确抑制)。在从一个 touch-actionpointerdown 到一个 pointerup 的事件序列中,手势识别可能需要等到该 pointerup
事件才能判断是否检测或忽略某个手势。因此,如果用户代理判定交互并非某一特定手势,则整段序列的兼容鼠标事件可能在最后一个 pointerup
事件后再一起派发。用户代理执行手势识别的这些细节不在本规范中定义,并且不同实现之间可能存在差异。
无论是否支持兼容鼠标事件,用户代理 MUST 始终支持 click、auxclick 与
contextmenu 事件,因为这些事件是 PointerEvent 类型,因此不是 兼容鼠标事件。在指针事件期间调用 preventDefault MUST NOT 影响 click、auxclick 或 contextmenu
是否被触发。
某些高层事件(例如 contextmenu、focus、blur)与指针事件的相对顺序未定义,且在不同用户代理之间有所不同。例如,在一些用户代理中,contextmenu
通常跟随一个 pointerup,而在另一些中它可能先于一个 pointerup 或 pointercancel,并且在某些情况它可能在没有任何对应指针事件的情况下触发(例如键盘交互的结果)。
此外,用户代理可使用自身启发式来决定是否触发 click、auxclick 或
contextmenu。某些用户代理在存在其他(非主)同类型指针或存在不同类型的其他主指针时可能选择不触发这些事件。用户代理可能判定某个动作不是“干净”的轻点、点击或长按(例如在触摸屏上手指接触期间移动过多)而决定不触发
click、auxclick 或 contextmenu。这些用户代理行为方面不在本规范定义范围内,且实现间可能不同。
除非另有说明,任何映射后的鼠标事件的目标 SHOULD 与对应指针事件的目标相同,除非该目标已不再参与其 ownerDocument
的树。在这种情况下,鼠标事件应在原始目标被移除时仍在树中的最近祖先节点上触发,即为该鼠标事件基于新目标节点构建新的事件路径。
作者可以通过取消 pointerdown 事件来阻止某些兼容鼠标事件的产生。
只有在指针按下时才能阻止鼠标事件。悬停指针(例如没有按钮按下的鼠标)其鼠标事件无法被阻止。
mouseover、mouseout、mouseenter 与 mouseleave
事件永远不会被阻止(即便指针处于按下状态)。
当指针事件的 EventListener 被设置为 passive
[DOM]
时,兼容鼠标事件不可被阻止。
虽然只有主指针能产生兼容鼠标事件,但多个主指针可以同时处于活动状态,各自产生自身的兼容鼠标事件。为兼容依赖 MouseEvents
的脚本,鼠标过渡事件(mouseover、mouseout、mouseenter 与
mouseleave)SHOULD 模拟单个传统鼠标输入的移动。这意味着每个事件目标的进入/退出状态应根据 [UIEVENTS] 规范有效。用户代理 SHOULD 通过在文档中维护传统鼠标指针的有效位置 (effective position of the legacy mouse pointer) 来实现。
在触发一个 pointerdown、pointerup 或
pointermove 事件,或在 window 上发生一个 pointerleave 事件之前,用户代理 SHOULD 执行以下步骤:
pointerdown、pointerup 或 pointermove 事件的目标。对于 pointerleave 事件,取消设置 T。mouseover、mouseout、mouseenter 与
mouseleave 事件。若当前 传统鼠标指针的有效位置 或 T
为未设置,将其视为窗口外的鼠标位置。
传统鼠标指针的有效位置
体现了:我们无法总是将指针过渡事件(pointerover、pointerout、pointerenter 与
pointerleave)直接映射到对应的传统鼠标过渡事件(mouseover、mouseout、mouseenter
与 mouseleave)。下动画展示一个场景:用户代理需要派发比指针过渡事件更多的传统鼠标过渡事件,以便使用单个传统鼠标输入协调两个主指针。
在该动画中,注意鼠标点击与触摸轻点之间的时间段。Button 1 未收到 pointerout 事件(因为“真实”鼠标指针在此期间没有离开按钮矩形),但当 传统鼠标指针的有效位置 在触摸轻点时移动到 Button
2,Button 1 收到一个 mouseout 事件。类似地,在触摸轻点与鼠标即将离开 Button 1 之间的时间段里,Button 1 未收到
pointerover 事件,但当 传统鼠标指针的有效位置 移回 Button 1
内部时,Button 1 收到一个 mouseover 事件。
每当用户代理要为支持悬停的设备派发一个指针事件时,它 SHOULD 执行以下步骤:
isPrimary 属性为 false,则派发该指针事件并终止这些步骤。pointerdown、pointerup 或 pointermove 事件,或在 window 上的 pointerleave 事件,则按 跟踪传统鼠标指针的有效位置
的描述派发兼容鼠标过渡事件。pointerdown 且事件被 canceled,则为该 pointerType 设置
PREVENT MOUSE EVENT 标志。
pointerType 没有设置 PREVENT MOUSE EVENT 标志,并且刚派发的指针事件是:
pointerdown,则触发 mousedown
事件。pointermove,则触发 mousemove
事件。pointerup,则触发 mouseup 事件。
pointercancel,则在 window
上触发 mouseup 事件。pointerup 或 pointercancel,则清除该 pointerType 的
PREVENT MOUSE EVENT 标志。
某些设备(如多数触摸屏)不支持在未激活状态悬停一个坐标(或一组坐标)。大量基于鼠标事件编写的现有内容假定事件由鼠标产生,因此通常成立以下特性:
mousemove 事件。这需要用户代理为此类输入设备提供不同的映射。每当用户代理要为一个 不支持悬停 的设备派发指针事件时,它 SHOULD 执行以下步骤:
isPrimary 属性为 false,则派发指针事件并终止这些步骤。pointerover 且该指针的 pointerdown 事件尚未派发,则触发一个 mousemove
事件(与遗留鼠标特定代码兼容)。pointerdown、pointerup 或 pointermove 事件,或在 window 上的 pointerleave 事件,则按 跟踪传统鼠标指针的有效位置
描述派发兼容鼠标过渡事件。pointerdown 且事件被 canceled,则为该 pointerType 设置
PREVENT MOUSE EVENT 标志。
pointerType 没有设置 PREVENT MOUSE EVENT 标志,并且刚派发的指针事件是:
pointerdown,则触发 mousedown
事件。pointermove,则触发 mousemove
事件。pointerup,则触发 mouseup 事件。
pointercancel,则在 window
上触发 mouseup 事件。pointerup 或 pointercancel,则清除该 pointerType 的
PREVENT MOUSE EVENT 标志。
如果用户代理同时支持 Touch Events(定义于 [TOUCH-EVENTS])与 Pointer Events,则 用户代理 MUST NOT 同时生成本节描述的兼容鼠标事件与 [TOUCH-EVENTS] 中概述的回退鼠标事件。
使用一个不支持悬停的主指针(例如触摸屏单指)激活元素(click)通常会产生如下事件序列:
mousemovepointeroverpointerentermouseovermouseenterpointerdownmousedownpointermove 与 mousemove
事件,取决于指针移动pointerupmouseuppointeroutpointerleavemouseoutmouseleaveclick然而,如果在此交互期间 pointerdown 事件被 canceled,则事件序列会变为:
mousemovepointeroverpointerentermouseovermouseenterpointerdownpointermove 事件,取决于指针移动pointeruppointeroutpointerleavemouseoutmouseleaveclick本附录讨论 Pointer Events 实现的安全与隐私注意事项。讨论范围仅限于直接由本规范定义的事件模型、API 与事件的实现所引发的安全与隐私问题。
本规范中定义的许多事件类型是响应用户操作而派发的。这允许恶意事件监听器获取用户通常认为是机密的信息,例如用户在页面上交互时鼠标 / 触控笔 / 手指的精确路径 / 移动。
指针事件包含额外信息(如果用户设备支持),例如笔输入的角度或倾斜、接触面的几何形状以及施加在触控笔或触摸屏上的压力。有关角度、倾斜、几何和压力的信息与用户设备上的传感器直接相关,这意味着本规范允许一个源访问这些传感器。
这些传感器数据以及确定使用的输入机制类型(鼠标、触摸、笔)的能力,可能被用来推断用户或设备及环境的特征。这些推断出的特征以及任何设备 / 环境信息本身可能是敏感的——例如,它们可能允许恶意站点进一步推断用户是否使用了辅助技术。这些信息也可能被用来构建用户档案以及尝试“指纹”并跟踪特定用户。
作为缓解,用户代理可考虑提供能力让用户禁用对特定传感器数据(例如角度、倾斜、压力)的访问,及/或只在用户明确选择后才提供这些数据。
本规范定义了作者访问“预测事件”的方法。规范本身并未定义用户代理应使用的预测算法。规范作者设想算法仅依赖与当前用户手势相关的前序指针事件。用户代理有责任确保其具体的预测算法实现不依赖任何额外数据——例如跨不同站点的完整交互历史——这些数据可能泄露用户的敏感信息或被用于“指纹”与跟踪。
除上述注意事项外,工作组认为本规范:
本节为非规范性内容。
buttons 属性为非零值的状态。鼠标:至少一个按钮被按下。触摸:与数字化仪发生物理接触。笔:与数字化仪物理接触,或在悬停时至少有一个按钮被按下。pointerId
的指针仍可能在文档内产生其他事件,则该指针仍被视为活动。示例:
preventDefault()、在事件处理器中返回 false 或其他 [UIEVENTS] 与
[HTML]
定义手段阻止的事件。可测属性表示与连续指针传感器数据相关的值,这些值以实数或来自大域的整数表达。对于指针事件,width、height、pressure、
tangentialPressure、tiltX、tiltY、twist、altitudeAngle、azimuthAngle
以及 [UIEVENTS] 鼠标事件模型中的
screenX、screenY、clientX、clientY 都是可测属性。
相比之下,pointerId、pointerType、isPrimary,以及
[UIEVENTS] 鼠标事件模型中的
button、buttons、ctrlKey、
shiftKey、altKey 与 metaKey 不被视为可测属性,因为它们不涉及传感器数据。
非常感谢众多人士提出的建议与推荐,其中一些已纳入本文档。工作组主席谨此感谢以下历任与现任的组员和参与者的贡献: Mustaq Ahmed, Arthur Barstow, Ben Boyle, Matt Brubeck, Rick Byers, Marcos Cáceres, Cathy Chan, Bo Cupp, Domenic Denicola, Ted Dinklocker, Adam Ettenberger, Robert Flack, Dave Fleck, Mike Fraser, Ella Ge, Olga Gerchikov, Scott González, Kartikaya Gupta, Dominique Hazael-Massieux, Philippe Le Hégaret, Hayato Ito, Patrick Kettner, Patrick H. Lauke, Scott Low, Sangwhan Moon, Masayuki Nakano, Olli Pettay, Addison Phillips, Alan Pyne, Antoine Quint, Jacob Rossi, Kagami Sascha Rosylight, Doug Schepers, Ming-Chou Shih, Brenton Simpson, Dave Tapuska, Liviu Tinta, Asir Vedamuthu, Lan Wei, Jeffrey Yasskin, Navid Zolghadr.
特别感谢为本模型第一版的开创做出贡献的人士,尤其包括:Charu Chandiram、Peter Freiling、Nathan Furtwangler、Thomas Olsen、Matt Rakow、Ramu Ramanathan、Justin Rogers、 Jacob Rossi、Reed Townsend 和 Steve Wright。
本节为非规范性内容。
以下是相较于 [PointerEvents3] 规范,本规范各版本发布之间实质性与主要编辑性变更的说明性摘要。 参见本规范编辑草案的完整修订历史。
WebIDLdictionary PointerEventInit : MouseEventInit {
long pointerId = 0;
double width = 1;
double height = 1;
float pressure = 0;
float tangentialPressure = 0;
long tiltX;
long tiltY;
long twist = 0;
double altitudeAngle;
double azimuthAngle;
DOMString pointerType = "";
boolean isPrimary = false;
long persistentDeviceId = 0;
sequence<PointerEvent> coalescedEvents = [];
sequence<PointerEvent> predictedEvents = [];
};
[Exposed=Window]
interface PointerEvent : MouseEvent {
constructor(DOMString type, optional PointerEventInit eventInitDict = {});
readonly attribute long pointerId;
readonly attribute double width;
readonly attribute double height;
readonly attribute float pressure;
readonly attribute float tangentialPressure;
readonly attribute long tiltX;
readonly attribute long tiltY;
readonly attribute long twist;
readonly attribute double altitudeAngle;
readonly attribute double azimuthAngle;
readonly attribute DOMString pointerType;
readonly attribute boolean isPrimary;
readonly attribute long persistentDeviceId;
[SecureContext] sequence<PointerEvent> getCoalescedEvents();
sequence<PointerEvent> getPredictedEvents();
};
partial interface Element {
undefined setPointerCapture (long pointerId);
undefined releasePointerCapture (long pointerId);
boolean hasPointerCapture (long pointerId);
};
partial interface mixin GlobalEventHandlers {
attribute EventHandler onpointerover;
attribute EventHandler onpointerenter;
attribute EventHandler onpointerdown;
attribute EventHandler onpointermove;
[SecureContext] attribute EventHandler onpointerrawupdate;
attribute EventHandler onpointerup;
attribute EventHandler onpointercancel;
attribute EventHandler onpointerout;
attribute EventHandler onpointerleave;
attribute EventHandler ongotpointercapture;
attribute EventHandler onlostpointercapture;
};
partial interface Navigator {
readonly attribute long maxTouchPoints;
};
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: