Copyright © 2025 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本规范中的功能扩展或修改了《Pointer Events》(指针事件)中的内容。Pointer Events 是一份由W3C(万维网联盟)推荐标准,描述了用于处理来自包括鼠标、手写笔或触摸屏等设备的与硬件无关的指针输入的事件及相关接口。为了兼容现有基于鼠标的内容,本规范还描述了为其他指针设备类型触发鼠标事件的映射。
本节描述了本文档在发布时的状态。当前W3C 出版物列表及本技术报告的最新修订版可在 W3C 标准和草案索引中找到。
本规范是对[PointerEvents2]的更新。
本次修订包含了新特性:
altitudeAngleazimuthAnglepointerrawupdate 事件,用于高频率事件本次修订还包括如下澄清:
pointerId 的策略与方法click、auxclick 和
contextmenu 事件的关系在本规范退出候选推荐阶段前,至少需有两个或更多独立实现通过每一项测试,尽管不要求每个实现都通过所有测试。
本文档由 Pointer Events 工作组 作为候选推荐快照发布, 并采用推荐流程。
作为候选推荐发布,不代表W3C及其成员的认可。候选推荐快照已接受广泛评审, 旨在收集实现经验,且工作组成员已承诺为实现提供 免版税授权。
本候选推荐计划最早于 2025年12月4日进入提议推荐阶段。
本文档由遵循 W3C 专利政策的工作组制作。 W3C维护了一份 与本组成果相关的任何专利披露的公开列表; 该页面还包含专利披露说明。任何个人若知晓某专利并认为其包含 必要声明, 必须按照 W3C专利政策第6节进行披露。
本文档受 2025年8月18日 W3C流程文档的管理。
本节为非规范性内容。
如今,大多数[HTML]内容都与鼠标输入相关,或者为鼠标输入所设计。那些以自定义方式处理输入的内容,通常会以[UIEVENTS]鼠标事件为基础进行编码。然而,现代计算设备除了鼠标,还集成了其他输入方式,例如触摸屏和手写笔。针对每一种输入方式,已经提出了相应的事件类型用于逐一处理。然而,当需要添加对新输入类型的支持时,这种方式往往会造成逻辑和事件处理的冗余,带来不必要的开销。内容如果只考虑了一种设备类型,也会造成兼容性问题。另外,为了兼容现有基于鼠标的内容,大多数用户代理会为所有输入类型触发鼠标事件。这使得 MouseEvent 难以区分是由实际鼠标设备产生,还是为兼容性由其它输入类型生成,导致难以同时兼容代码处理不同设备类型。
为了减少多输入类型编码的复杂度,并解决上述 MouseEvent 的歧义问题,本规范定义了一种更抽象的输入形式,称为指针。指针可以是由鼠标光标、手写笔、触摸(包括多点触控)或其他指向型输入设备在屏幕上产生的任意接触点。 这种模型可以让网站和应用无论用户使用哪种硬件,都能良好工作。如果需要针对具体设备进行处理,本规范还定义了检查产生事件的设备类型的属性。主要目标是提供一组统一的事件和接口,让开发者能更轻松地进行跨设备指针输入编程,仅在确需增强体验时再进行设备专用的处理。
另一个关键目标是,使多线程用户代理可以响应直接操作动作,如平移和缩放(比如在触摸屏上用手指或手写笔),且不会因脚本执行而阻塞。
用于处理通用指针输入的事件类似于鼠标事件:pointerdown、
pointermove、pointerup、pointerover、pointerout
等。这样有助于内容从 Mouse Events 平滑迁移到 Pointer Events。
Pointer Events 在包含 Mouse Events 所有常用属性(如客户端坐标、目标元素、按键状态)的基础上,还针对压力、接触几何、倾斜等输入形式提供了新属性。作者可以方便地以 Pointer
Events 复用不同输入类型的处理逻辑,仅在需要时为某种输入类型定制特殊体验。
指针事件虽然来源于多种输入设备,但其本身不是由其他设备专有事件生成的定义。虽然允许并推荐为兼容性目的这样做,但本规范不要求支持其他设备专有事件(如鼠标事件或触摸事件)。用户代理可以只支持指针事件而完全不支持其它事件。为兼容那些基于鼠标事件开发的内容,本规范还增加了可选章节,描述如何基于鼠标以外的指针输入生成兼容性鼠标事件。
本规范未对同时支持 Touch Events(定义见[TOUCH-EVENTS])和 Pointer Events 的用户代理所应有的行为提供建议。 关于这两类规范之间的关系,可以参见 Touch Events 社区组。
除标注为非规范性的部分外,本规范中的全部作者指南、图示、示例和注释均属于非规范性内容。规范性内容为本规范中的其他内容。
本文档中的关键词MAY、MUST、MUST NOT、OPTIONAL和SHOULD,应按照 BCP 14 [RFC2119] [RFC8174] 的说明进行解释,且仅当这些词以本文所示全部大写方式出现时如此。
本节为非规范性内容。
以下是一些基础示例,展示了本规范中部分 API 可如何被作者使用。更多、更具体的示例请见本文相关章节。
/* 绑定 Pointer Events 或传统触摸/鼠标事件 */
if (window.PointerEvent) {
// 支持 Pointer Events 时,仅监听指针事件
target.addEventListener("pointerdown", function(e) {
// 如有必要,可根据 e.pointerType 分别处理手触/手写笔/鼠标逻辑
// 以适配不同行为
...
});
...
} else {
// 传统触摸/鼠标事件处理函数
target.addEventListener('touchstart', function(e) {
// 阻止兼容性鼠标事件和点击事件
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);
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;
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;
[SecureContext] sequence<PointerEvent> getCoalescedEvents();
sequence<PointerEvent> getPredictedEvents();
};
pointerId导致事件的指针的唯一标识符。用户代理可以
(MAY) 为主鼠标指针保留一个通用的 pointerId 值 0
或 1。值 -1 必须 (MUST)
保留并用于指示由非指向设备生成的事件。对于其他指针,用户代理可自由采用不同策略和方法分配 pointerId
值。但在活动指针所属的顶级浏览上下文(参见
[HTML])中,所有标识符必须唯一,且该标识符不得 (MUST
NOT)受其它顶级浏览上下文影响(即某顶级上下文不能假设指针移出其上下文进入另一上下文时
pointerId
会保持不变)。
用户代理可以 (MAY)回收之前已退休的活动指针的值,或可以
(MAY)总是为特定指针设备重用同一个 pointerId
(例如在多用户协作应用中唯一识别特定用户的某支手写笔)。然而在后一种情形中,为最小化跨页面或域的指纹与跟踪风险,该 pointerId必须 (MUST)仅在该页面/会话生命周期内与该特定设备显式关联;在新会话再次使用该设备时必须
(MUST)选择一个新的随机化 pointerId。
width指针的接触几何在 X 轴的宽度(CSS 像素,参见 [CSS21])。该值对给定指针的每个事件可以 (MAY)更新。对于通常缺乏接触几何的输入(如传统鼠标),以及硬件未检测到实际几何的情况,用户代理必须 (MUST)返回默认值 1。
height指针的接触几何在 Y 轴的高度(CSS 像素,参见 [CSS21])。该值对给定指针的每个事件可以 (MAY)更新。对于通常缺乏接触几何的输入(如传统鼠标),以及硬件未检测到实际几何的情况,用户代理必须 (MUST)返回默认值 1。
pressure指针输入的归一化压力,范围 [0,1],其中 0 和 1
分别表示硬件可检测的最小与最大压力。对不支持压力的硬件或平台,当处于活动按钮状态时该值必须 (MUST)为 0.5,否则为 0。
tangentialPressure归一化切向压力(亦称笔筒压力),通常由额外控制(例如喷笔触控笔上的指轮)设定,范围 [-1,1],0
为控制的中性位置。部分硬件可能只支持正区间 [0,1]。不支持切向压力的硬件或平台该值必须 (MUST)为
0。
tiltX平面角(度,范围 [-90,90]),为 Y-Z 平面与包含传感器轴(如笔/触控笔)及 Y 轴的平面之间的角度。正 tiltX
向右(X 增加方向)。tiltX 可与 tiltY 组合表示相对于数字化仪法线的倾斜。未报告倾斜/角度的硬件与平台该值必须 (MUST)为 0。
tiltX。
tiltY平面角(度,范围 [-90,90]),为 X-Z 平面与包含传感器轴及 X 轴的平面之间的角度。正 tiltY 朝向用户(Y
增加方向)。tiltY 可与 tiltX 组合表示相对于数字化仪法线的倾斜。未报告倾斜/角度的硬件与平台该值必须 (MUST)为 0。
tiltY。
twist传感器(如笔/触控笔)围绕其主轴的顺时针旋转角(度,范围 [0,359])。未报告该值的硬件与平台必须
(MUST)返回 0。
altitudeAngle传感器在范围 [0,π/2] 的仰角(弧度)—— 0 表示与表面(X-Y 平面)平行,π/2
表示垂直。未报告倾斜/角度的硬件与平台该值必须 (MUST)为 π/2。
altitudeAngle = π/4(距 X-Y 平面 45 度)。azimuthAngle传感器的方位角(弧度),范围 [0, 2π]——0 表示传感器笔帽指向 X 增加方向(俯视时“3
点钟”位置),顺时针递增(π/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)提供其他设备类型的规范值。
isPrimary指示该指针是否表示此指针类型的主指针。
getCoalescedEvents()返回合并事件列表的方法。
getPredictedEvents()返回预测事件列表的方法。
PointerEventInit 字典用于 PointerEvent 构造函数,以提供构建不受信任(合成)指针事件的机制。它继承自
[UIEVENTS] 中定义的 MouseEventInit
字典。参见 示例 了解触发不受信任指针事件的示例代码。
事件构造步骤中,PointerEvent 会克隆 PointerEventInit 的 coalescedEvents 到 合并事件列表,并克隆其 PointerEventInit 的 predictedEvents 到 预测事件列表。
PointerEvent 接口继承自 MouseEvent(定义见
UI
Events)。另请注意 CSSOM View Module 中提出的扩展:将各种坐标属性从
long 改为 double 以允许小数坐标。若用户代理对 PointerEvent 实现了此扩展而未对常规 MouseEvent 实现,则在
click、
auxclick 与 contextmenu 事件方面有额外要求。
在多指针(例如多触点)场景中,isPrimary 用于在每个指针类型的一组活动指针中识别主指针。
pointerType
一个),多个指针被视为主指针。例如同时移动一个触摸接触与一个鼠标光标会产生两个被视为主指针的指针。isPrimary 为 false 的事件。触发指针事件名为 e,意为使用 PointerEvent 按 PointerEvent 接口与 属性与默认操作 中定义的属性设置来 触发事件。
若该事件不是 gotpointercapture、
lostpointercapture、click、
auxclick 或 contextmenu,则为此 PointerEvent 运行 处理待定指针捕获 步骤。
确定事件目标步骤如下:
令 targetDocument 为目标的 节点文档 [DOM]。
若事件为 pointerdown、pointermove 或 pointerup,则将该事件的 pointerId 的活动文档设为
targetDocument。
若事件是 pointerdown,关联设备为直接操作设备且目标是
Element,则按照 隐式指针捕获 中描述,为此 pointerId 设置指针捕获为目标元素。
在触发事件之前,用户代理应当 (SHOULD)将目标视为指针从
previousTarget 移动到该目标,用于确保事件顺序 [UIEVENTS]。若 needsOverEvent 标志已设,即便目标元素相同也需要一个 pointerover。
向确定的目标触发事件。
保存确定的目标为该指针的 previousTarget,并重置 needsOverEvent 为
false。若 previousTarget 不再 已连接 [DOM],则沿事件路径更新
previousTarget 为最近的仍 已连接 的父节点,并设置
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。建议这些用户代理在 PointerEvent 中将(继承的)这些属性值设为
null,以引导作者使用标准替代(target 与 relatedTarget)。
类似 MouseEvent 的 relatedTarget,
relatedTarget 应初始化为指针刚刚离开的元素(pointerover 或
pointerenter)或指针正进入的元素(pointerout 或 pointerleave)。其它指针事件该值默认为
null。注意当元素获得指针捕获后,该指针的后续事件都视为在捕获元素边界内。
对 gotpointercapture 与 lostpointercapture,除表中定义属性外,其余属性应与导致用户代理运行
处理待定指针捕获步骤并触发上述事件的指针事件一致。
用户代理必须 (MUST)在隐式释放指针捕获时以及触发非 gotpointercapture / lostpointercapture 的指针事件时运行以下步骤。
lostpointercapture 的指针事件。gotpointercapture 的指针事件。如 click、
auxclick 与 contextmenu 事件章节定义,即便 lostpointercapture 已分派,对应的
click、auxclick 或 contextmenu(若存在)仍会分派给捕获目标。
当 用户代理
检测到页面不太可能继续接收具有特定 pointerId 的指针事件时,必须 (MUST)抑制指针事件流。以下任一场景满足条件(可能还有其它场景):
touch-action CSS
属性章节。
pointercancel。pointerout。pointerleave。当指向设备相对屏幕表面移动或其某属性变化时,会按 指针事件类型定义触发各种事件。对于静止的指向设备(既未移动也无属性变化),在布局变化影响该指针的 用户代理必须 (MUST)触发特定边界事件以反映命中测试目标变化,参见
pointerover、
pointerenter、
pointerout 与
pointerleave。出于性能(例如避免监听器引发大量命中测试或布局变更)考虑,用户代理可以 (MAY)延迟这些边界事件。
pointermove。Pointer Events 包含两组互补属性表达传感器相对 X-Y 平面的朝向:tiltX / tiltY(原始规范引入),以及
azimuthAngle / altitudeAngle(采纳自 Touch Events - Level 2)。
根据具体硬件与平台,用户代理通常只会接收到其中一组值(tiltX / tiltY 或
altitudeAngle / azimuthAngle)。用户代理必须
(MUST)使用以下算法转换这些值。
当用户代理从 azimuthAngle / altitudeAngle 计算
tiltX / tiltY 时,应当 (SHOULD)使用
Math.round [ECMASCRIPT] 规则进行整数取整。
/* 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)。当发生以下任一情况时,用户代理必须 触发名为 pointerenter 的指针事件:
pointerdown 事件之前(参见 pointerdown)。当指针进入活动按钮状态时,用户代理必须 触发名为 pointerdown
的指针事件。对于鼠标,这是设备从未按下任何按钮过渡到至少按下一个按钮时。对于触摸,这是与数字化仪产生物理接触时。对于触控笔,这是触控笔在未按下任何按钮的情况下与数字化仪产生物理接触,或在悬停时从未按下任何按钮过渡到至少按下一个按钮。
对于不支持悬停的输入设备,用户代理必须在分派 pointerdown 事件之前,先分别触发名为 pointerover 和 pointerenter 的指针事件。
pointerdown 事件(当
isPrimary 属性为 true 时)来阻止某些兼容性鼠标事件的触发。这会在该指针上设置
PREVENT MOUSE EVENT 标志。但请注意,这并不会阻止
mouseover、mouseenter、mouseout 或
mouseleave 事件的触发。
当指针改变任何不会触发
pointerdown 或 pointerup 事件的属性时,用户代理必须 触发名为 pointermove
的指针事件。这包括对坐标、压力、切向压力、倾斜、旋转、接触几何(width 与 height)或组合按钮的任何更改。
用户代理可以延迟分派 pointermove 事件(例如出于性能原因)。
对于单个已分派的 pointermove 事件,其合并事件信息将通过 getCoalescedEvents 方法暴露。
此类事件的最终坐标应被用于确定事件的目标。
当指针改变任何不会触发
pointerdown 或 pointerup 的属性时,且仅在安全上下文中,
用户代理必须 触发名为
pointerrawupdate 的指针事件。
这些属性列表参见 pointermove 事件。
与 pointermove 相比,用户代理应当尽可能快且以 JavaScript 能处理的最高频率分派 pointerrawupdate 事件。
由于 pointermove 事件可能被延迟或被合并,用于确定
target 的事件最终位置可能不同于其被合并的事件,因此 pointerrawupdate 事件的
target 可能与
pointermove 的不同。
注意:如果事件循环中已经存在另一个具有相同 pointerId 且尚未分派的 pointerrawupdate,则用户代理可以将新的 pointerrawupdate 与该事件合并,而不是创建新任务。
这可能导致 pointerrawupdate 具有合并事件,并且它们会在该事件在事件循环中被处理时,作为单个 pointerrawupdate 的合并事件一并投递。更多信息参见 getCoalescedEvents。
关于 pointerrawupdate 与 pointermove 的顺序:如果用户代理从平台收到的更新会导致两者都触发,则用户代理必须先分派 pointerrawupdate,再分派相应的 pointermove。
除 target 外,自上一次 pointermove 起已分派的所有 pointerrawupdate 的合并事件列表的串联,在其它事件属性方面,与下一次
pointermove 的合并事件相同。
pointerrawupdate 的属性与 pointermove 基本一致,差别在于 cancelable
对于 pointerrawupdate必须为 false。
用户代理应当不为 兼容性鼠标事件触发 pointerrawupdate。
pointerrawupdate
添加监听器可能会对网页性能产生负面影响,具体取决于用户代理的实现。
对于大多数用例,其他指针事件类型已足够。
只有当 JavaScript 需要高频事件且能同样快速处理时,才应添加 pointerrawupdate
监听器。
在这些情况下,很可能无需监听其他类型的指针事件。当指针离开活动按钮状态时,用户代理必须 触发名为 pointerup
的指针事件。对于鼠标,这是从至少一个按钮按下过渡为没有按钮按下。对于触摸,这是与数字化仪解除物理接触。对于触控笔,这是在没有按钮按下的情况下,触控笔与数字化仪解除物理接触,或在悬停时从至少一个按钮按下过渡为没有按钮按下。
对于不支持悬停的输入设备,用户代理必须在分派 pointerup 事件之后,依次触发名为 pointerout 和 pointerleave 的指针事件。
所有 pointerup 事件的 pressure 值均为
0。
当检测到需要抑制指针事件流的场景时,用户代理必须 触发名为 pointercancel 的指针事件。
pointercancel 事件下列属性的值必须与具有相同 pointerId
的上一个已分派指针事件的值相匹配:width、height、pressure、tangentialPressure、tiltX、tiltY、twist、altitudeAngle、azimuthAngle、pointerType、isPrimary,以及从
[UIEVENTS] 继承的坐标。pointercancel 事件中的
coalescedEvents 和
predictedEvents 列表必须为空,且该事件的 cancelable 属性必须为 false。
当发生以下任一情况时,用户代理必须 触发名为 pointerout 的指针事件:
当发生以下任一情况时,用户代理必须 触发名为 pointerleave 的指针事件:
pointerup 事件之后(参见 pointerup)。当某元素获得指针捕获时,用户代理必须 触发名为 gotpointercapture
的指针事件。该事件在接收指针捕获的元素上触发。随后该指针的事件将投递到该元素。参见 设置指针捕获 与 处理待定指针捕获 章节。
当某个指针的指针捕获被释放后,用户代理必须 触发名为 lostpointercapture 的指针事件。该事件必须在捕获释放后该指针的任何后续事件之前触发。该事件在移除指针捕获的元素上触发。除 click、auxclick
与 contextmenu 事件外,该指针的后续事件将遵循常规命中测试机制(不在本规范范围内)来确定事件目标。参见 释放指针捕获、隐式释放指针捕获 与 处理待定指针捕获 章节。
以下章节描述对现有 Element 接口的扩展,以便于设置与释放指针捕获。
WebIDLpartial interface Element {
undefined setPointerCapture (long pointerId);
undefined releasePointerCapture (long pointerId);
boolean hasPointerCapture (long pointerId);
};
setPointerCapture()为参数 pointerId 标识的指针,在调用此方法的元素上设置指针捕获。对于该指针的后续事件,捕获目标将取代正常的命中测试结果,仿佛指针始终位于捕获目标之上,并且它们必须始终定向到此元素,直到捕获被释放。为使此方法生效,指针必须处于其活动按钮状态,否则将静默失败。当所提供的方法参数与任何活动指针均不匹配时,throw 一个 "NotFoundError" DOMException。
releasePointerCapture()从调用此方法的元素上,为参数 pointerId
标识的指针释放指针捕获。该指针的后续事件将遵循常规的命中测试机制(不在本规范范围内)来确定事件目标。当所提供的方法参数与任何活动指针均不匹配时,throw 一个 "NotFoundError" DOMException。
hasPointerCapture指示调用此方法的元素是否对参数 pointerId 标识的指针具有指针捕获。具体而言,若 待定指针捕获目标覆盖 对于 pointerId
已设置为调用此方法的元素,则返回 true;否则返回 false。
gotpointercapture 事件,在调用 setPointerCapture()
之后,此方法会立即返回 true。因此,它可用于在 pointerdown
事件监听器内部检测隐式指针捕获。以下章节描述对现有 GlobalEventHandlers
混入的扩展,以便于事件处理程序的注册。
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-y ] |
manipulation
|
| 初始值: | auto |
| 适用元素: | 除:非替换内联元素、表格行、行组、表格列与列组 之外的所有元素 |
| 是否继承: | 否 |
| 百分比: | 不适用 |
| 媒体: | 视觉 |
| 计算值: | 与指定值相同 |
| 规范顺序: | 按语法 |
| 动画类型: | 不可动画 |
touch-action CSS 属性决定直接操作交互(尽管属性名如此,但不限于触摸)可以触发用户代理的平移与缩放行为。参见
touch-action 的取值 一节。
在开始平移或缩放之前,若以下条件均为真,用户代理必须抑制指针事件流:
pointerdown 事件,且pointerdown 之后)发送 pointerup 或 pointercancel 事件。touch-action 不会应用/级联到嵌入的浏览上下文中。例如,即便对一个 <iframe> 应用了
touch-action,也不会对该 <iframe> 内的平移与缩放直接操作行为产生任何影响。
当用户使用直接操作指针(如触摸屏上的触摸或触控笔)与某元素交互时,该输入的效果由
touch-action 属性的值,以及该元素与其祖先的默认直接操作行为共同决定,如下:
touch-action。注意,如果对元素应用了 CSS 变换,其坐标空间可能与屏幕坐标不同,从而影响此处的符合性;例如,相对于屏幕旋转 90 度的元素的
X 轴将与屏幕坐标的 Y 轴平行。touch-action 属性时,该平移才被支持。
document 元素(见 [HTML])之间每个元素的 touch-action 属性时,该缩放才被支持。
touch-action 值的改变都会被忽略。例如,在
pointerdown 处理脚本中将某元素的
touch-action 值从 auto 改为
none,不会导致用户代理在该指针仍处于活动状态时中止或抑制该输入的任何平移或缩放行为。
touch-action 的 pan-*
取值,一旦用户代理在手势开始时已确定是否直接处理该手势,则在该指针处于活动状态期间,对同一手势方向的后续改变用户代理应当忽略。例如,若某元素设置为
touch-action: pan-y(意味着仅垂直平移由用户代理处理),而手势起始为水平移动,即便用户在手指仍接触屏幕时将手势方向改为垂直,也不应发生垂直平移。
touch-action 取值进行处理或关联的方法超出本规范范围。
touch-action 属性涵盖与视口平移与缩放相关的直接操作行为。任何其他用户代理行为(例如文本选择/高亮,或激活链接与表单控件)不得受该 CSS 属性影响。
auto 或 none 取值行为的交互或手势,均不在本规范范围内。
pan-x 或 pan-y)时,平移过程中不能改变轴。
touch-action 取值 定义见 [COMPAT]。方向特定的 pan 取值有助于自定义某些过度滚动行为。例如,为实现简单的“下拉刷新”,当滚动位置为 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;">
This element receives pointer events for all direct manipulation interactions that otherwise lead to panning or zooming.
</div>
<div style="touch-action: pan-x;">
This element receives pointer events when not panning in the horizontal direction.
</div>
<div style="overflow: auto;">
<div style="touch-action: none;">
This element receives pointer events for all direct manipulation interactions that otherwise lead to panning or zooming.
</div>
<div>
Direct manipulation interactions on this element MAY be consumed for manipulating the parent.
</div>
</div>
<div style="overflow: auto;">
<div style="touch-action: pan-y;">
<div style="touch-action: pan-x;">
This element receives pointer events for all direct manipulation interactions because
it allows only horizontal panning yet an intermediate ancestor
(between it and the scrollable element) only allows vertical panning.
Therefore, no direct manipulation behaviors for panning/zooming are
handled by the user agent.
</div>
</div>
</div>
本节为非规范性内容。
指针捕获允许特定指针(包括任何兼容性鼠标事件)的事件被重定向到正常命中测试结果之外的特定元素。这在自定义滑块控件等场景中很有用(例如类似 [HTML]
<input type="range"> 控件)。可以在滑块拇指元素上设置指针捕获,使用户即便将指针滑出拇指区域,也能来回拖动控件。
pointerdown
之后,可以使用指针捕获,让用户即使指针稍微偏离拇指,也能继续滑动。通过调用 element.setPointerCapture(pointerId) 方法,可在类型为 element 的 Element 上设置指针捕获。
当调用此方法时,用户代理必须执行以下步骤:
pointerId 与任何活动指针均不匹配,则抛出“NotFoundError” DOMException。pointerId 指定的活动指针。
InvalidStateError” DOMException。pointerLockElement),则抛出“InvalidStateError” DOMException。
pointerId,将待定指针捕获目标覆盖设置为调用该方法的 Element。pointerdown 监听器中尝试释放隐式指针捕获但失败的情形。
通过调用 element.releasePointerCapture(pointerId) 方法可在元素上显式释放指针捕获。调用该方法时,用户代理必须执行以下步骤:
pointerId 与任何活动指针均不匹配,且这些步骤并非因隐式释放指针捕获而被调用,则抛出“NotFoundError” DOMException。pointerId 的 Element,其 hasPointerCapture 为 false,则终止这些步骤。
pointerId,若已设置,则清除 待定指针捕获目标覆盖。实现用于平移与缩放的直接操作交互的输入(如触摸屏上的触摸或触控笔)应当表现得与在调用任何 pointerdown 监听器之前,在目标元素上调用了 setPointerCapture 完全一致。可使用 hasPointerCapture API(例如在 pointerdown 监听器中)来判断此情况是否已发生。如果在下一个指针事件触发之前未对该指针调用
releasePointerCapture,则将按常规向目标分派
gotpointercapture 事件以表明捕获已生效。
在分派完 pointerup 或 pointercancel 事件后,用户代理必须清除该刚刚分派的 pointerup 或 pointercancel 事件对应的 pointerId 的 待定指针捕获目标覆盖,
随后运行 处理待定指针捕获 步骤,以在必要时触发 lostpointercapture。
完成 处理待定指针捕获 步骤后,若该指针支持悬停,用户代理必须另外发送相应的边界事件,以反映无捕获状态下指针的当前位置。
当指针捕获目标覆盖不再已连接 [DOM] 时,指针捕获目标覆盖应当设置为文档。
当待定指针捕获目标覆盖不再已连接 [DOM] 时,应当清除此 待定指针捕获目标覆盖 节点。
lostpointercapture
事件。
当在某元素上成功应用指针锁 [PointerLock] 时,若当前有任何元素已设置捕获或处于待捕获状态,用户代理必须按调用了 releasePointerCapture 方法的方式运行相应步骤。
出于性能原因,用户代理可能选择不在指针的每一次 pointermove
发生时都发送事件,即每次某个 可度量属性
(如坐标、压力、切向压力、倾斜、旋转或接触几何)更新时都发送。相反,它们可以将多次变化合并为单个 pointermove 或 pointerrawupdate 事件。此方式有助于减少用户代理必须执行的事件处理量(必须执行的处理),但会自然降低追踪指针位置的粒度与精度,尤其在快速且幅度大的移动中。使用
getCoalescedEvents
方法,应用可以访问原始的、未合并的位置变化,从而获得更精细的指针移动处理。在绘图应用中,未合并事件可用于绘制更平滑的曲线,更贴近指针的真实移动。
pointermove
事件的合并坐标(灰色圆点)时,曲线明显更棱角且粗糙;使用
getCoalescedEvents() 提供的更细粒度红色圆点绘制出的同一条线则更加平滑,更接近实际指针移动。PointerEvent 具有关联的 合并事件列表(零个或多个 PointerEvent 的列表)。对于可信的 pointermove 与
pointerrawupdate 事件,该列表是所有被合并进该事件的
PointerEvent 序列。“父”可信 pointermove 与 pointerrawupdate
事件表示这些合并事件的累积,但可能经过额外处理(例如与显示刷新率对齐)。因此,这些事件的合并事件列表总是至少包含一个事件。对其它可信事件类型,该列表为空。非可信事件的
合并事件列表初始化为传入构造函数的值。
可信事件的合并事件列表中的事件具有以下特征:
timeStamp 值 [DOM]——所有合并事件的 timeStamp 小于或等于触发
getPredictedEvents
方法的已分派指针事件的时间戳。合并事件列表必须按时间戳排序,首个事件拥有最小时间戳。
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>
所有这些已分派事件的顺序必须与原始事件的真实发生顺序一致。举例:如果一次 pointerdown 导致合并的 pointermove 事件分派,则用户代理必须先分派一个包含该 pointerId 所有合并事件的 pointermove,然后再分派 pointerdown。
以下示例展示按递增 timeStamp
值发生的实际事件与用户代理分派事件:
| 实际事件 | 已分派事件 |
|---|---|
pointer (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2) 含 1
个合并事件 |
pointer (pointerId=1)
坐标变化 |
pointerrawupdate (pointerId=1) 含 1
个合并事件 |
pointer (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2) 含 1
个合并事件 |
pointer (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2) 含 1
个合并事件 |
pointer (pointerId=1)
坐标变化 |
pointerrawupdate (pointerId=1) 含 1
个合并事件 |
pointer (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2) 含 1
个合并事件 |
pointer (pointerId=1) 按钮按下
|
pointermove (pointerId=1) 含 2
个合并事件pointermove (pointerId=2) 含 4
个合并事件pointerdown (pointerId=1) 含 0
个合并事件 |
pointer (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2) 含 1
个合并事件 |
pointer (pointerId=2)
坐标变化 |
pointerrawupdate (pointerId=2) 含 1
个合并事件 |
pointer (pointerId=1) 按钮释放
|
pointermove (pointerId=2) 含 2
个合并事件pointerup (pointerId=1) 含 0
个合并事件 |
一些用户代理具备内建算法,在一系列已确认指针移动之后,可以依据当前手势之前的事件及移动速度/轨迹,对未来指针位置做出预测。应用可利用
getPredictedEvents
方法使用这些信息,提前“预绘”到预测位置,以降低感知延迟,并在收到真实点后丢弃这些预测点。
pointermove
事件的合并坐标,并显示用户代理预测的未来点(灰色圆点)。PointerEvent 具有关联的 预测事件列表(零个或多个
PointerEvent)。对于可信的 pointermove 事件,它是用户代理预测将要跟随该事件发生的一系列
PointerEvent;其它可信事件类型则为空。非可信事件的
预测事件列表初始化为构造函数传入的值。
列表中的事件数量以及它们距离当前时间戳的时间范围由用户代理及其预测算法决定。
可信事件的预测事件列表中的事件具有以下特征:
timeStamp 值
[DOM]——所有预测事件的 timeStamp 大于或等于触发
getPredictedEvents
方法的已分派指针事件时间戳。预测事件列表必须按时间戳排序,首个事件拥有最小时间戳。
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 被创建时,用户代理应当针对其 合并事件列表与 预测事件列表中的每个事件执行以下步骤:
pointerId、
pointerType、
isPrimary 以及 isTrusted
设置为与“父”指针事件对应属性一致。
cancelable 与 bubbles 设为
false(这些事件永远不会单独分派)。PointerEvent 值。当可信 PointerEvent 的 target 被改变时,用户代理应当对其
合并事件列表与 预测事件列表中的每个事件执行:
当今绝大多数现存 Web 内容仅使用 Mouse Events 编码。以下描述用户代理如何将通用指针输入映射为鼠标事件以兼容这些内容的算法。
与鼠标事件的兼容性映射是本规范的一个可选功能。建议用户代理支持此功能以获得对遗留内容的最佳兼容性。
在高层概念上,兼容性鼠标事件旨在与相应的指针事件“交织”出现。然而,这一特定顺序并非强制,支持兼容性鼠标事件的用户代理可以延迟或批量分派鼠标事件,只要它们的相对顺序保持一致。
尤其在触摸屏输入情况下,用户代理可以采用额外启发式进行手势识别(除非被作者通过 显式抑制)。在一次从 touch-actionpointerdown 到 pointerup 的事件序列中,手势识别可能需要等到 pointerup 事件才能确认或忽略某手势。结果是整个序列的兼容性鼠标事件可能在最后一个 pointerup
事件后一起分派,如果用户代理判定该交互不属于特定手势。这些手势识别细节不在本规范范围内,各实现可能不同。
无论是否支持兼容性鼠标事件,用户代理必须始终支持 click、auxclick 与
contextmenu 事件,因为它们属于 PointerEvent 类型,因此不是 兼容性鼠标事件。在指针事件中调用
preventDefault 不得影响是否触发 click、auxclick 或
contextmenu。
某些高层事件(如 contextmenu、focus、blur)与指针事件的相对顺序未定义,且在不同用户代理间差异存在。例如,某些用户代理中
contextmenu 常常跟随 pointerup,而另一些则常常先于 pointerup 或 pointercancel,还有情形可能在无任何对应指针事件下触发(例如由键盘交互导致)。
此外,用户代理可能应用自身启发式来决定是否应触发 click、auxclick 或
contextmenu。某些用户代理可能在存在其它(非主)同类型指针或其它类型主指针时选择不触发;也可能认定某动作不是“干净”的点击/长按(例如触摸屏上手指接触期间移动过多)而决定不触发这些事件。这些行为不在本规范定义范围内,可能因实现不同而不同。
除非另有说明,任何映射的鼠标事件的目标应当与其对应指针事件的目标相同,除非该目标已不再参与其 ownerDocument
的树。在这种情况下,鼠标事件应在其被移除时仍在树中的最近祖先节点上触发,并基于新目标节点构建新的事件路径。
作者可以通过取消 pointerdown 事件来阻止产生某些兼容性鼠标事件。
只有当指针处于按下状态时才能阻止鼠标事件产生。悬停指针(例如按钮未按下的鼠标)其鼠标事件不可被阻止。
mouseover、mouseout、mouseenter 与 mouseleave
事件永远不会被阻止(即使指针按下)。
当指针事件的 EventListener 被设置为 passive
时,兼容性鼠标事件无法被阻止 [DOM]。
尽管只有 主指针 能产生兼容性鼠标事件,但 多个主指针 可以同时激活,各自生成兼容性鼠标事件。为兼容依赖 MouseEvents
的脚本,鼠标过渡事件(mouseover、mouseout、mouseenter、mouseleave)应当模拟单一传统鼠标输入的移动。这意味着每个事件目标的进入/离开状态符合 [UIEVENTS]。用户代理应当通过在文档中维护 传统鼠标指针的有效位置 来保证这一点。
在触发 pointerdown、pointerup、
pointermove 或在 window 上触发 pointerleave 事件之前,用户代理应当执行以下步骤:
pointerdown、pointerup 或 pointermove 事件的目标;对 pointerleave 事件,取消设置 T。mouseover、mouseout、mouseenter 与 mouseleave,模拟从当前
传统鼠标指针的有效位置 移动到 T
的鼠标。将未设置的当前位置或 T 视作窗口外位置。
传统鼠标指针的有效位置
模型反映无法总是将指针过渡事件(pointerover、pointerout、pointerenter、pointerleave)直接映射到相应的传统鼠标过渡事件(mouseover、mouseout、mouseenter、mouseleave)。下方动画展示用户代理为了用单一传统鼠标输入协调两个主指针,需要分派比指针过渡事件更多的传统鼠标过渡事件的情形。
在该动画中,注意鼠标点击与触摸点击之间的时间段。按钮 1 未接收 pointerout(因为“真实”鼠标指针在此期间未离开按钮矩形),但当触摸点击使 传统鼠标指针的有效位置 移动到按钮 2 时,按钮 1 接收
mouseout。类似地,在触摸点击与鼠标离开按钮 1 之前的时间段,按钮 1 未接收 pointerover,但当 传统鼠标指针的有效位置 回到按钮 1 内部时,按钮 1
接收 mouseover。
每当用户代理要为支持悬停的设备分派指针事件时,应当执行以下步骤:
isPrimary 属性为 false,则分派该指针事件并结束。pointerdown、pointerup、pointermove,或在 window 上的 pointerleave,则按照 跟踪传统鼠标指针的有效位置
描述分派兼容性鼠标过渡事件。pointerdown 且事件被 取消,则为该 pointerType 设置
PREVENT MOUSE EVENT 标志。
pointerType 未设置 PREVENT MOUSE EVENT 标志,且刚分派的事件是:
pointerdown:触发 mousedown。
pointermove:触发 mousemove。
pointerup:触发 mouseup。pointercancel:在 window
上触发 mouseup。pointerup 或 pointercancel,则清除该 pointerType 的
PREVENT MOUSE EVENT 标志。
某些设备(如多数触摸屏)不支持在非活动状态悬停一个坐标(或坐标集合)。大量仅编码为鼠标事件的现有内容假设事件来自鼠标,因此以下性质通常成立:
mousemove。这要求用户代理为此类输入设备提供不同的映射。每当用户代理要为 不支持悬停 的设备分派指针事件时,应当执行以下步骤:
isPrimary 属性为 false,则分派该指针事件并结束。pointerover 且尚未为该指针分派 pointerdown,则触发一次
mousemove(与遗留仅鼠标代码兼容)。
pointerdown、pointerup、pointermove 或在 window 上的 pointerleave,则按 跟踪传统鼠标指针的有效位置
描述分派兼容性鼠标过渡事件。pointerdown 且事件被 取消,则为该 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,用户代理不得同时生成本节描述的兼容性鼠标事件以及 fallback mouse events(见 [TOUCH-EVENTS])。
使用一个不支持悬停的主指针(如单指触摸屏)激活元素(发生 click)通常产生以下事件序列:
mousemovepointeroverpointerentermouseovermouseenterpointerdownmousedownpointermove 与
mousemove(取决于指针移动)
pointerupmouseuppointeroutpointerleavemouseoutmouseleaveclick如果在此交互期间 pointerdown 被 取消,则事件序列变为:
mousemovepointeroverpointerentermouseovermouseenterpointerdownpointermove(取决于指针移动)pointeruppointeroutpointerleavemouseoutmouseleaveclick本附录讨论 Pointer Events 实现的安全与隐私注意事项。讨论范围仅限于直接源自本规范所定义的事件模型、API 与事件实现的安全和隐私问题。
本规范中所定义的许多事件类型是响应用户行为而分派的。这使得恶意的事件监听器可以获取用户通常认为是机密的信息,例如用户在页面交互时鼠标 / 触控笔 / 手指的精确路径与移动轨迹。
指针事件包含额外信息(取决于用户设备支持),例如笔输入的角度或倾斜、接触面的几何形状、以及对触控笔或触摸屏施加的压力。关于角度、倾斜、几何和压力的信息与用户设备上的传感器直接相关,这意味着本规范允许某个来源访问这些传感器。
这些传感器数据,以及判定所用输入机制类型(鼠标、触摸、笔)的能力,都可能被用来推断用户或用户设备/环境的特征。这些推断出的特征以及任何设备/环境信息本身可能是敏感的——例如,它们可能让恶意站点进一步推断用户是否在使用辅助技术。这些信息还可能被用于建立用户画像、以及尝试“指纹识别”并追踪某个特定用户。
作为缓解措施,用户代理可考虑提供让用户禁用访问特定传感器数据(如角度、倾斜、压力)的能力,和/或仅在用户明确选择加入后才提供这些数据。
传感器的出厂校准信息 可能基于传感器数据的特定波动与特征用于对单个设备进行指纹识别。虽然本规范将许多与传感器相关的事件属性定义为
float 与 double 精度,但我们建议实现限制其暴露的传感器数据到实际有用的精度:
0.1 度本规范定义了作者访问“预测事件”的方法。本规范本身不定义用户代理用于预测的算法。规范作者设想这些算法只依赖于当前手势之前的相关指针事件。用户代理有责任确保其具体的预测算法实现不会依赖任何额外数据——例如用户在不同站点上的完整交互历史——这些数据可能泄露用户的敏感信息或被用于“指纹识别”与追踪。
除上述注意事项外,工作组认为本规范:
本节为非规范性内容。
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。
本节为非规范性内容。
以下为本规范各版本之间的重大与实质性编辑更改的说明性摘要,相对 [PointerEvents2] 规范。参见 本规范编辑草稿的完整修订历史。
touch-action 取值
(pan-left、pan-right、pan-up、pan-down)click、auxclick 与
contextmenu 的 .button 与 .buttons 行为来自 UIEventsisTrusted 位
pointercancel 坐标 / 属性的澄清
click/contextmenu 不是兼容鼠标事件pointerId 注释中的部分内容移入规范文本pointercancel 的触发行为pointermove 的描述
pointerrawmove 预测事件列表为空
touch-action 的原理与目的touch-action 定义(明确限定仅平移 /
缩放行为)click/contextmenu
与用户代理启发式的注释添加更多信息pointerId 从
0 改为 -1touch-action 与 iframe /
嵌入浏览上下文的注释azimuthAngle、altitudeAngle、tiltX、tiltY 使其不再要求在
pointer events Web IDL 中具有默认值pointerrawupdate 与
getCoalescedEvents 添加安全上下文标准click、auxclick、contextmenu 类型改为 PointerEventaltitudeAngle/azimuthAnglegetPredictedEvents API。
getCoalescedEvents API 与
pointerrawupdate 事件。touch-action 取值
(pan-left、pan-right、pan-up、pan-down),并澄清现有
pan-x 与 pan-y 的行为。
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;
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;
[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:
Referenced in:
Referenced in: