指针事件 (Pointer Events)

第 4 级 (Level 4)

W3C 工作草案

关于本文档的更多细节
本版本:
https://www.w3.org/TR/2025/WD-pointerevents4-20251022/
最新发布版本:
https://www.w3.org/TR/pointerevents4/
最新编辑草稿:
https://w3c.github.io/pointerevents/
历史:
https://www.w3.org/standards/history/pointerevents4/
提交历史
测试套件:
https://wpt.fyi/pointerevents/
最新推荐规范:
https://www.w3.org/TR/pointerevents2
编辑:
Patrick H. Lauke (TetraLogical)
Robert Flack (Google)
前任编辑:
Matt Brubeck (Mozilla)
Rick Byers (Google)
Navid Zolghadr (Google)
反馈:
GitHub w3c/pointerevents (拉取请求, 新问题, 未关闭问题)
public-pointer-events@w3.org 主题行使用 [pointerevents4] … 消息主题 … (归档)
浏览器支持:
caniuse.com

摘要

本规范中的功能扩展或修改了“Pointer Events”(W3C 推荐规范)中用于处理来自鼠标、触控笔或触摸屏等设备的、与硬件无关的指针输入的事件及相关接口。为与现有基于鼠标的内容兼容,本规范还描述了将其他指针设备类型映射为触发 Mouse Events 的方式。

本文档状态

本节描述本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订可在 W3C 标准与草案索引 中找到。

本规范是对 [PointerEvents3] 的更新。其包含编辑性澄清以及支持更多用例的新特性。

本文档由 Pointer Events Working Group 按照 Recommendation track 作为工作草案发布。

作为工作草案发布并不意味着得到 W3C 及其成员的认可。

本文档为草案,可能在任何时间被更新、替换或被其他文档废弃。除作为一项在研工作外,不宜将本文档作为其他用途引用。

本文档由遵循 W3C 专利政策(Patent Policy) 运作的小组制作。 W3C 维护了与本小组可交付成果相关的 专利披露公开列表; 该页面也包含披露专利的说明。任何个人若确知某项专利且认为该专利包含 必要声明(Essential Claim), 必须依据 W3C 专利政策》第 6 节 披露相关信息。

本文档受 2025 年 8 月 18 日 W3C 流程文档(Process Document) 约束。

1. 介绍

本节为非规范性内容。

如今,大多数 [HTML] 内容是与鼠标输入配合使用和/或为其设计的。以自定义方式处理输入的内容通常会针对 [UIEVENTS] 鼠标事件进行编码。然而,如今较新的计算设备引入了其他形式的输入,包括触摸屏和笔输入。已经有人提出为这些输入形式各自定义事件类型来处理它们。然而,这种方法在为一种新的输入类型添加支持时,往往会带来不必要的逻辑重复和事件处理开销。当内容仅以某一种设备类型为前提进行编写时,这也经常带来兼容性问题。此外,为了与现有的基于鼠标的内容兼容,大多数用户代理会为所有输入类型触发鼠标事件。这使得很难判断一个鼠标事件究竟表示真实的鼠标设备,还是为了兼容性由其他输入类型生成的,从而难以同时针对两种设备类型编写代码。

为降低同时支持多种输入类型的开发成本,并帮助缓解上述有关鼠标事件的歧义性,本规范定义了一种更抽象的输入形式,称为指针。指针可以是屏幕上的任何接触点,由鼠标光标、手写笔、触摸(包括多点触控)或其他指向型输入设备产生。该模型使得无论用户拥有何种硬件,都更易于编写可良好运行的网站和应用。对于需要设备特定处理的场景,本规范也定义了用于检查产生事件的设备类型的属性。其主要目标是提供一组统一的事件与接口,使跨设备的指针输入更易于编写,同时在需要增强体验时仍允许仅在必要时进行设备特定的处理。

另一个关键目标是,使多线程的用户代理能够在不阻塞脚本执行的情况下,处理用于平移和缩放的直接操控动作(例如在触摸屏上使用手指或手写笔)。

尽管本规范为多种指针输入定义了统一的事件模型,但该模型并不涵盖键盘或类键盘接口等其他形式的输入(例如,运行在仅触摸屏设备上的屏幕阅读器或类似辅助技术,允许用户按顺序在可聚焦控件和元素间导航)。虽然用户代理也可能选择在响应这些接口时生成指针事件,但这种情形不在本规范涵盖范围内。

首先,鼓励作者通过响应诸如 focusblurclick 等高级事件,为所有形式的输入提供等效功能。然而,在使用低级事件(如指针事件)时,鼓励作者确保支持所有类型的输入。对于键盘和类键盘接口,这可能需要添加显式的键盘事件处理。参见 键盘可达性 [WCAG22] 了解更多详情。

指针输入将鼠标、手写笔和触摸等多种输入源结合在一起
1 指针是一种与硬件无关的表示形式,用于表述能够在屏幕上定位到特定坐标(或一组坐标)的输入设备。

用于处理通用指针输入的事件与鼠标事件非常相似:pointerdownpointermovepointeruppointeroverpointerout 等。这有助于将内容从鼠标事件轻松迁移到指针事件。 指针事件在提供鼠标事件中常见属性(包括客户端坐标、目标元素、按钮状态)的同时,还新增了适用于其他输入形式的属性,如压力、接触几何尺寸、倾斜角等。作者可以方便地针对指针事件编写代码,在合理的地方在不同输入类型之间共享逻辑,并仅在需要获得最佳体验时,对特定输入类型进行定制化处理。

虽然指针事件来自多种输入设备,但它们并未被定义为由其他设备特定事件生成。兼容性方面这是可行并被鼓励的,但本规范并不要求必须支持其他设备特定事件(如鼠标事件或触摸事件)。用户代理可以仅支持指针事件而不支持任何其他设备事件。为兼容基于鼠标特定事件编写的内容,本规范确实提供了一个可选部分,描述如何基于来自非鼠标设备的指针输入生成兼容鼠标事件

本规范不就同时支持触摸事件(参见 [TOUCH-EVENTS])和指针事件的用户代理的预期行为提供任何建议。关于这两份规范之间关系的更多信息,请参阅 触摸事件社区小组

2. 一致性

除标记为非规范性的章节外,本规范中的所有编写指南、图示、示例和注记均为非规范性内容。本规范中的其他所有内容均为规范性内容。

本文档中的关键词 MAYMUSTMUST NOTOPTIONALSHOULD 的解释如 BCP 14 [RFC2119] [RFC8174] 所述,并且当且仅当这些词以此处所示的全大写形式出现时,才按该解释进行理解。

3. 示例

本节为非规范性内容。

以下是一些基础示例,展示了作者可能如何使用本规范中的部分 API。更多、更具体的示例见本文档相关章节。

示例 1: 特性检测与事件绑定
/* 绑定到指针事件或传统的触摸/鼠标 */

if (window.PointerEvent) {
    // 如果支持指针事件,只监听指针事件
    target.addEventListener("pointerdown", function(e) {
        // 如有需要,基于 e.pointerType 应用不同的逻辑
        // 以应对触摸/笔/鼠标的不同行为
        ...
    });
    ...
} else {
    // 传统的触摸/鼠标事件处理器
    target.addEventListener('touchstart', function(e) {
        // 阻止兼容鼠标事件和 click
        e.preventDefault();
        ...
    });
    ...
    target.addEventListener('mousedown', ...);
    ...
}

// 用于键盘处理的附加事件监听器
...
示例 2: 检测用户的输入类型
window.addEventListener("pointerdown", detectInputType);

function detectInputType(event) {
    switch(event.pointerType) {
        case "mouse":
            /* 检测到鼠标输入 */
            break;
        case "pen":
            /* 检测到手写笔输入 */
            break;
        case "touch":
            /* 检测到触摸输入 */
            break;
        default:
            /* pointerType 为空(无法检测)或特定于 UA 的自定义类型 */
    }
}
示例 3: 调整元素大小以匹配接触几何尺寸
<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>
示例 4: 从脚本触发不受信任的指针事件
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);
示例 5: 在 PointerDown 时分配笔的颜色
<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>

4. Pointer Events 和接口

4.1 PointerEvent 接口

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 为主鼠标指针保留一个通用的 pointerId01。用于表示并非由指点设备生成的事件的 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],其中 01 分别表示硬件可检测到的最小与最大压力。对于不支持压力的硬件与平台,当处于active buttons state 时该值MUST0.5,否则为 0

tangentialPressure

指针输入的归一化切向压力(亦称“桶压”),通常由额外的控制器(例如喷笔触控笔上的指轮)设置,范围 [-1,1],其中 0 为控制器的中立位置。注意某些硬件可能仅支持 [0,1] 的正值。对于不支持切向压力的硬件与平台,该值MUST0

尽管属性名包含“压力”,但在实践中生成此属性值的硬件控制/传感器不一定具备压力敏感性。例如,多数喷笔/绘画触控笔的指轮通常可自由设置,而无需持续施压以防止其回到零位。
tiltX

Y-Z 平面与同时包含换能器(例如笔/触控笔)轴与 Y 轴的平面之间的夹角(以度为单位,范围 [-90,90])。正的 tiltX 指向右侧(X 值增大的方向)。tiltX 可与 tiltY 结合使用,以表示相对于数字化仪法线的倾斜。对于不报告倾斜或角度的硬件与平台,该值MUST0

tiltX 说明示意图
2 正的 tiltX
tiltY

X-Z 平面与同时包含换能器(例如笔/触控笔)轴与 X 轴的平面之间的夹角(以度为单位,范围 [-90,90])。正的 tiltY 朝向用户(Y 值增大的方向)。tiltY 可与 tiltX 结合使用,以表示相对于数字化仪法线的倾斜。对于不报告倾斜或角度的硬件与平台,该值MUST0

tiltY 说明示意图
3 正的 tiltY
twist

换能器(例如笔/触控笔)绕自身主轴的顺时针旋转角度(以度为单位,范围 [0,359])。对于不报告旋转的硬件与平台,该值MUST0

altitudeAngle

换能器(例如笔/触控笔)的仰角(弧度),范围 [0,π/2]——其中 0 表示与表面(X-Y 平面)平行,π/2 表示垂直于表面。对于不报告倾斜或角度的硬件与平台,该值MUSTπ/2

这里为 altitudeAngle 定义的默认值是 π/2,表示换能器垂直于表面。这与 Touch Events - Level 2 规范对 altitudeAngle 属性的定义不同,后者的默认值为 0
altitudeAngle 说明示意图
4 示例 altitudeAngleπ/4(相对 X-Y 平面 45°)。
azimuthAngle

换能器(例如笔/触控笔)的方位角(弧度),范围 [0, 2π]——其中 0 表示换能器笔帽沿 X 值增大的方向(若从正上方观察,指向“3 点钟”)在 X-Y 平面上,顺时针角度逐渐增大(π/2 在“6 点钟”,π 在“9 点钟”,3π/2 在“12 点钟”)。当换能器完全垂直于表面(altitudeAngleπ/2)时,该值MUST0。对于不报告倾斜或角度的硬件与平台,该值MUST0

azimuthAngle 说明示意图
5 示例 azimuthAngleπ/6(“4 点钟”)。
pointerType

指示导致该事件的设备类型(如鼠标、笔、触摸)。如果用户代理要为鼠标、笔/触控笔或触摸输入设备触发指针事件,则 pointerType 的取值MUST 符合下表:

指针设备类型 pointerType
鼠标 mouse
笔 / 触控笔 pen
触摸接触 touch

如果用户代理无法检测设备类型,则该值MUST 为空字符串。若用户代理支持上述列表之外的指针设备类型,pointerType 的取值SHOULD 加供应商前缀,以避免不同设备类型之间名称冲突。后续规范MAY 为其他设备类型提供更多规范化取值。

参见 示例 2 了解如何使用 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:将 PointerEventInitcoalescedEvents 克隆到合并事件列表,并将 PointerEventInitpredictedEvents 克隆到预测事件列表

PointerEvent 接口继承自 UI Events 中定义的 MouseEvent。另请注意 CSSOM View Module 中的拟议扩展,它将各种坐标属性由 long 改为 double,以允许小数坐标。对于已对 PointerEvent 实现该扩展、但尚未对常规 MouseEvent 实现的用户代理,在处理clickauxclickcontextmenu 事件时有额外要求。

4.1.1 按钮状态

4.1.1.1 复合按键交互

某些指点设备(如鼠标或笔)支持多个按钮。在 [UIEVENTS] 的鼠标事件模型中,每次按键都会产生 mousedownmouseup 事件。为更好地抽象这种硬件差异并简化跨设备的输入编写,Pointer Events 不会为复合按键按下(当设备上已有按钮按下时再按下额外按钮)触发重叠的 pointerdownpointerup 事件。

相反,可通过检查 buttonbuttons 属性的变化来检测复合按键的按下。buttonbuttons 属性继承自 MouseEvent 接口,但其语义与取值有所变化,详见以下小节。

buttonbuttons 的修改仅适用于指针事件。然而,对 clickauxclickcontextmenubuttonbuttons 的取值MUST 遵循 [UIEVENTS],与兼容鼠标事件相同。

4.1.1.2 button 属性

为识别任意指针事件中的按钮状态变化(不仅仅是 pointerdownpointerup),button 属性指示其状态变更触发该事件的设备按钮。

设备按钮变化 button
自上次事件以来,按钮与触摸/笔接触均未变化 -1
左键,
触摸接触,
笔接触
0
中键 1
右键,
笔侧键
2
X1(后退)鼠标键 3
X2(前进)鼠标键 4
笔橡皮擦按钮 5
在鼠标拖拽期间,pointermove 事件中的 button 值与 mousemove 事件中的不同。例如,当按住右键移动鼠标时,pointermovebutton 为 -1,而 mousemovebutton 为 2。
4.1.1.3 buttons 属性

buttons 属性以位掩码的形式给出设备按钮的当前状态(与 MouseEvent 相同,但可能值集合更大)。

设备按钮当前状态 buttons
未按任何按钮时移动鼠标
悬停期间未按任何按钮时移动笔
0
左键,
触摸接触,
笔接触
1
中键 4
右键,
笔侧键
2
X1(后退)鼠标键 8
X2(前进)鼠标键 16
笔橡皮擦按钮 32

4.1.2 主指针

在多指针(例如多点触控)场景中,isPrimary 属性用于在每种指针类型的活动指针集合中标识一个主指针。

  • 任意时刻,对于每种指针类型最多只有一个主指针。
  • 某一指针类型中第一个变为活动的指针(例如多点触控交互中首先触摸屏幕的手指)成为该类型的主指针。
  • 只有主指针会产生兼容鼠标事件。若存在多个主指针,这些指针都会产生兼容鼠标事件
希望实现单指交互的作者可以通过忽略非主指针来达成(不过,请参见下面关于多个主指针的说明)。
当同时使用两种或更多指点设备类型时,可存在多个主指针(每种 pointerType 一个)。例如,同时移动触摸接触与鼠标光标会产生两个都被视为主指针的指针。
某些设备、操作系统与用户代理可能会忽略并行使用多种指针输入,以避免误触。例如,同时支持触摸与笔交互的设备,可能在笔处于活动时忽略触摸输入,使用户可在使用笔时将手掌放在触摸屏上(通常称为“掌压抑制”)。目前,作者无法抑制这种行为。
在某些情况下,用户代理可能会触发没有任何指针被标记为主指针的指针事件。例如,当某一类型存在多个活动指针(如多点触控交互),且主指针被移除(例如离开屏幕)时,可能最终没有主指针。此外,在一些平台上,主指针由设备上的同类型所有活动指针共同决定(包括那些目标为用户代理之外应用的指针),如果第一个(主)指针在用户代理之外,而其他(非主)指针目标在用户代理内,则用户代理MAY 为其他指针触发事件,并将 isPrimary 设为 false
当前的操作系统与用户代理通常没有“多个鼠标输入”的概念。当存在多于一个的鼠标设备(例如同时配有触控板与外接鼠标的笔记本电脑)时,通常将所有鼠标设备视为单一设备——来自任意设备的移动被转化为单一鼠标指针的移动,且不同鼠标设备的按键按下并无区分。因此通常只有单一鼠标指针,且该指针为主指针。

4.1.3 使用 PointerEvent 接口触发事件

触发名为 e 的指针事件”指使用 PointerEvent触发一个名为 e 的事件,并按PointerEvent 接口与属性与默认操作中的定义设置其属性。

若该事件不是 gotpointercapturelostpointercaptureclickauxclickcontextmenu,则为该 PointerEvent 运行处理挂起的指针捕获步骤。

确定要触发事件的目标如下:

targetDocument 为目标的节点文档 [DOM]。

如果事件为 pointerdownpointermovepointerup,则将该事件的 活动文档设置为 targetDocument

如果事件为 pointerdown,关联设备为直接操控设备,且目标为 Element,则按照隐式指针捕获的描述,为该 pointerId 对目标元素设置指针捕获

在触发该事件之前,为了确保事件顺序 [UIEVENTS],用户代理SHOULD 将目标视为指点设备已从 previousTarget 移动到该目标。若 needsOverEvent 标志被设置,则即使目标元素相同也需要一个 pointerover 事件。

向确定的目标触发事件。

将确定的目标保存为该指针的 previousTarget,并将 needsOverEvent 标志重置为 false。若在任意时刻 previousTarget 不再连接[DOM],则沿着向 previousTarget 分派事件对应的事件路径,更新 previousTarget 为最近的仍连接[DOM]的父节点,并将 needsOverEvent 标志设为 true

使用指针捕获目标覆盖作为目标,而非常规命中测试结果,可能会触发一些由 [UIEVENTS] 定义的边界事件。这相当于指针离开其先前目标并进入新的捕获目标。释放捕获时也可能发生同样情况,即指针离开捕获目标并进入命中测试目标。
4.1.3.1 属性与默认操作

本规范中定义的事件类型的 bubblescancelable 属性及其默认操作如下表所示。有关各事件类型的详细信息,见指针事件类型

事件类型 冒泡 可取消 默认操作
pointerover
pointerenter
pointerdown 不定:当指针为主指针时,与 mousedown 事件的所有默认操作相同
取消该事件也会阻止后续兼容鼠标事件的触发。
pointermove 不定:当指针为主指针时,与 mousemove 的所有默认操作相同
pointerrawupdate
pointerup 不定:当指针为主指针时,与 mouseup 的所有默认操作相同
pointercancel
pointerout
pointerleave
gotpointercapture
lostpointercapture

视口操作(平移与缩放)——通常源于直接操控交互——有意不作为指针事件的默认操作,这意味着这些行为(例如在触摸屏上移动手指导致页面平移)不能通过取消指针事件来抑制。作者必须改用 touch-action 显式声明文档区域的直接操控行为。移除对事件取消的依赖有助于用户代理进行性能优化。

对于 pointerenterpointerleave 事件,composed [DOM] 属性SHOULDfalse;对于表中其他指针事件,该属性SHOULDtrue

对于上表中的所有指针事件,detail [UIEVENTS] 属性SHOULD 为 0。

许多用户代理在 MouseEvents 中暴露非标准属性 fromElementtoElement 以支持遗留内容。我们鼓励这些用户代理在 PointerEvents 中将这些(继承的)属性值设为 null,以引导作者改用标准化替代项(targetrelatedTarget)。

类似 MouseEventrelatedTargetrelatedTarget 应初始化为指针刚离开的元素(对于 pointeroverpointerenter 事件),或指针即将进入的元素(对于 pointeroutpointerleave 事件)。对于其他指针事件,该值默认为 null。注意,当一个元素获得指针捕获后,该指针的后续所有事件都被视为发生在捕获元素的边界内部。

对于 gotpointercapturelostpointercapture 事件,除上表定义的属性外,其他所有属性应与导致用户代理运行处理挂起的指针捕获步骤并触发 gotpointercapturelostpointercapture 的指针事件相同。

4.1.3.2 处理挂起的指针捕获

用户代理MUST隐式释放指针捕获时,以及在触发非 gotpointercapturelostpointercapture 的指针事件时,运行以下步骤:

  1. 若该指针的指针捕获目标覆盖已设置,且不等于挂起的指针捕获目标覆盖,则在该指针捕获目标覆盖节点上触发名为 lostpointercapture 的指针事件。
  2. 若该指针的挂起的指针捕获目标覆盖已设置,且不等于指针捕获目标覆盖,则在该挂起的指针捕获目标覆盖上触发名为 gotpointercapture 的指针事件。
  3. 若已设置,则将 指针捕获目标覆盖设为挂起的指针捕获目标覆盖;否则,清除指针捕获目标覆盖

clickauxclickcontextmenu 事件一节所述,即使已派发 lostpointercapture 事件,若存在对应的 clickauxclickcontextmenu 事件,它们仍会派发到捕获目标。

4.1.3.3 抑制指针事件流

用户代理MUST 在检测到网页不太可能继续接收具有某个特定 pointerId 的指针事件时,抑制该指针事件流。以下任一情形满足该条件(也MAY 存在其他情形):

  • 用户代理打开了模态对话框或菜单。
  • 指针输入设备被物理断开,或可悬停的指针输入设备(例如可悬停的笔/触控笔)离开了数字化仪可检测的悬停范围。
  • 随后用户代理使用该指针来操控页面视口(例如平移或缩放)。详见 touch-action CSS 属性一节。
    用户代理可通过多种指针类型(如触摸与笔)触发平移或缩放,因此平移或缩放的开始可能导致对多种指针(包括不同 pointerType 的指针)的抑制。
  • 作为 [HTML] 中拖放处理模型定义的拖动操作启动算法的一部分,针对引发拖动操作的该指针。

用户代理MAY 抑制指针事件流的其他情形包括:

  • 在指针活动期间更改了设备的屏幕方向。
  • 用户尝试使用超过设备支持数量的同时指针输入进行交互。
  • 用户代理将该输入解释为误触(例如硬件支持掌压抑制)。

检测上述任何情形的方法不在本规范范围内。

用户代理MUST 按以下步骤抑制指针事件流

4.1.4 由布局变化引起的边界事件

相对于屏幕表面移动或其属性发生变化的指点设备,会触发指针事件类型中定义的各种事件。对于静止的指点设备(既未相对屏幕表面移动,也未发生任何属性变化),当布局变化影响到该指针的命中测试目标时,用户代理MUST 触发某些边界事件,详见 pointeroverpointerenterpointeroutpointerleave。出于性能考虑(例如避免过多的命中测试或由边界事件监听器引起的布局变化),用户代理MAY 延迟触发这些边界事件。

静止的指点设备(既未相对屏幕表面移动,也未发生任何属性变化)不会触发 pointermove 事件。

4.1.5 tiltX / tiltYaltitudeAngle / azimuthAngle 之间转换

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 的规则对最终整数值进行取整。

Example 6: Converting between tiltX/tiltY and altitudeAngle/azimuthAngle
/* 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};
}

4.2 指针事件类型 (Pointer Event types)

下文列出了本规范中定义的事件类型。

对于主指针,这些事件(除 gotpointercapturelostpointercapture 外)也可能触发兼容鼠标事件

4.2.1 pointerover 事件

当出现以下任一情况时,用户代理MUST 触发一个名为 pointerover 的指针事件:

4.2.2 pointerenter 事件

当出现以下任一情况时,用户代理MUST 触发一个名为 pointerenter 的指针事件:

该事件类型与 pointerover 类似,但有两点不同:pointerenter 不冒泡,且其分派会考虑后代元素的 命中测试 边界。
该事件类型与 [UIEVENTS] 中的 mouseenter 事件及 [CSS21] 描述的 CSS :hover 伪类存在相似性。另见 pointerleave 事件。

4.2.3 pointerdown 事件

当指针进入 活动按钮状态 时,用户代理MUST 触发一个名为 pointerdown 的指针事件。鼠标:从无按钮按下转为至少一个按钮按下。触摸:与 数字化仪 产生物理接触。笔:在未按任意按钮时与数字化仪接触,或在悬停时从无按钮按下转为至少一个按钮按下。

对于鼠标(或其他多按钮指针设备),这意味着 pointerdownpointerup 的触发情形并不与 mousedownmouseup 完全一致。更多信息参见 复合按键

对于不支持悬停的输入设备用户代理MUST 在派发 pointerdown 事件之前,按顺序触发名为 pointeroverpointerenter 的指针事件。

作者可以通过取消 pointerdown 事件(如果 isPrimarytrue)来阻止某些兼容鼠标事件的触发。这会在该指针上设置 PREVENT MOUSE EVENT 标志。但这不会阻止 mouseovermouseentermouseoutmouseleave 事件的触发。

4.2.4 pointermove 事件

当指针发生任何不会触发 pointerdownpointerup 事件的属性变化时,用户代理MUST 触发一个名为 pointermove 的指针事件。包括坐标、压力、切向压力、倾斜、旋转、接触几何(widthheight)或 复合按键 的变化。

用户代理MAY 延迟派发 pointermove(例如出于性能原因)。延迟期间的 合并事件 信息会通过单一派发的 pointermovegetCoalescedEvents 方法暴露。事件最终坐标用于确定事件目标。

4.2.5 pointerrawupdate 事件

当指针发生任何不会触发 pointerdownpointerup 的属性变化时,并且仅在安全上下文内,用户代理MUST 触发一个名为 pointerrawupdate 的指针事件。相关属性列表参见 pointermove

pointermove 相反,用户代理SHOULD 尽快且尽可能频繁派发 pointerrawupdate,频率取决于 JavaScript 能否处理。

由于 pointermove 事件可能被延迟或合并,pointerrawupdatetarget 可能与 pointermove 不同,因为用于确定目标的最终位置可能与其合并事件不同。

注意:如果已经存在另一个具有相同 pointerId 且尚未在事件循环中派发的 pointerrawupdate用户代理MAY 将新的 pointerrawupdate 合并到该事件中,而不是创建新任务。这可能导致 pointerrawupdate 具有合并事件,并在事件循环中处理时一次性作为该事件的合并事件交付。详见 getCoalescedEvents

关于 pointerrawupdatepointermove 的排序:如果用户代理收到的平台更新同时导致 pointerrawupdatepointermove,则用户代理MUST 先派发 pointerrawupdate,再派发对应的 pointermove

target 外,自上次 pointermove 事件以来所有已派发 pointerrawupdate 的合并事件列表拼接结果,与下一次 pointermove 的合并事件在其他属性上等价。pointerrawupdate 的大多数属性与 pointermove 相同,唯一不同是 cancelable 对于 pointerrawupdateMUST 为 false。

用户代理SHOULD 不为 pointerrawupdate 触发兼容鼠标事件

pointerrawupdate 添加监听器可能对页面性能产生负面影响,具体取决于用户代理实现。大多数用例下其它指针事件类型已足够。仅当需要高频事件且能快速处理时才应添加 pointerrawupdate 监听器,这些情况下通常无需监听其他指针事件类型。

4.2.6 pointerup 事件

当指针离开 活动按钮状态 时,用户代理MUST 触发一个名为 pointerup 的指针事件。鼠标:从至少一个按钮按下转为无按钮按下。触摸:从 数字化仪 移除物理接触。笔:在无按钮按下情况下移除与数字化仪的物理接触,或在悬停时从至少一个按钮按下转为无按钮按下。

对于不支持悬停的输入设备用户代理MUST 在派发 pointerup 之后,依次触发名为 pointeroutpointerleave 的指针事件。

所有 pointerup 事件的 pressure 值均为 0

如果指针当前被捕获,用户代理MUST 还要 隐式释放指针捕获

对于鼠标(或其他多按钮指针设备),这意味着 pointerdownpointerup 的触发情形并不与 mousedownmouseup 完全一致。更多信息参见 复合按键

4.2.7 pointercancel 事件

当检测到需要抑制指针事件流的情形时,用户代理MUST 触发一个名为 pointercancel 的指针事件。

pointercancel 事件中以下属性的值MUST 与最后一个具有相同 pointerId 的已派发指针事件一致: widthheightpressuretangentialPressuretiltXtiltYtwistaltitudeAngleazimuthAnglepointerTypeisPrimary 以及从 [UIEVENTS] 继承的坐标。pointercancel 事件中的 coalescedEventspredictedEvents 列表MUST 为空,事件的 cancelableMUST 为 false。

4.2.8 pointerout 事件

当出现以下任一情况时,用户代理MUST 触发一个名为 pointerout 的指针事件:

4.2.9 pointerleave 事件

当出现以下任一情况时,用户代理MUST 触发一个名为 pointerleave 的指针事件:

该事件类型与 pointerout 类似,但有两点不同:pointerleave 不冒泡,且其分派会考虑后代元素的 命中测试 边界。
该事件类型与 [UIEVENTS] 中的 mouseleave 事件及 [CSS21] 描述的 CSS :hover 伪类存在相似性。另见 pointerenter 事件。

4.2.10 gotpointercapture 事件

当某元素获得指针捕获时,用户代理MUST 触发一个名为 gotpointercapture 的指针事件。该事件在获得指针捕获的元素上触发。此后该指针的事件将派发到此元素。参见 设置指针捕获处理挂起的指针捕获 部分。

4.2.11 lostpointercapture 事件

当指针捕获被释放后,用户代理MUST 触发一个名为 lostpointercapture 的指针事件。此事件MUST 在释放捕获后、该指针的任何后续事件之前触发。事件在移除捕获的元素上触发。该指针后续事件(除 clickauxclickcontextmenu 外)通过常规命中测试确定目标。参见 释放指针捕获隐式释放指针捕获处理挂起的指针捕获 部分。

4.2.12 clickauxclickcontextmenu 事件

本节是在 [UIEVENTS] 中定义的 clickauxclickcontextmenu 事件基础上的补充。这些事件通常与用户界面激活相关,也可能由非指针输入设备(如键盘)触发。

这些事件MUST 属于 PointerEvent 类型,并受本节其余部分附加要求的约束。

4.2.12.1 事件属性

对于这些事件,除 pointerIdpointerType 外,所有在本规范中定义的 PointerEvent 专有属性MUST 取其默认值。此外:

  • 如果事件由指点设备生成,其 pointerIdpointerTypeMUST 与引发这些事件的 PointerEvents 相同。
  • 如果由非指点设备(例如语音识别软件或键盘交互)生成,pointerIdMUST-1pointerTypeMUST 为空字符串。
4.2.12.2 事件坐标

PointerEvent 所述,CSSOM View Module 提议将各种坐标属性(screenXscreenYpageXpageYclientXclientYxyoffsetXoffsetY)重定义为 double 以支持小数坐标。然而,当该变更仅应用于 PointerEvent 而未应用于常规 MouseEvent 时,会在 clickauxclickcontextmenu 情形下导致某些遗留代码的兼容性问题。为此,已在 CSSOM View Module 中仅对 PointerEvent 实现该提议变更的用户代理MUST 使用 Math.floor [ECMASCRIPT] 将上述坐标属性转换为 long(如原始 UI Events 中定义)。

4.2.12.3 事件派发

clickauxclickcontextmenu 事件MUST 遵循 [UIEVENTS] 规范中的派发流程,只是事件目标使用如下算法覆盖:

  1. event 为正在派发的 clickauxclickcontextmenu 事件,userEvent 为导致触发 event 的用户交互事件。

    userEvent 可能不是 PointerEvent;例如,通过按下复选框上的空格键触发 click 时,它是一个 KeyboardEvent

    userEventPointerEvent 时:对于 clickauxclick,它是一个 pointerup;对于 contextmenu,它是 pointerdownpointerup(取决于原生平台约定)。

  2. 如果 userEvent 不是 PointerEvent,则按 [UIEVENTS] 规范派发 event,不覆盖其目标,并跳过余下步骤。
  3. 如下定义 target

    如果 eventcontextmenu 事件,或 userEvent 在相应指针被捕获时派发,则令 targetuserEvent 的目标。

    否则(eventclickauxclickuserEvent 为未被捕获派发的 pointerup)则令 target 为在派发 event 时 DOM 中对应 pointerdownpointerup 目标的最近公共包含祖先。

  4. 按 [UIEVENTS] 规范向 target 派发 event

    如果 userEvent 曾被捕获,即使具有相同 pointerIdlostpointercapture 已派发,event 仍派发到 userEvent 的捕获目标。

5. Element 接口的扩展

下节描述对现有 Element 接口的扩展,以便于设置和释放指针捕获。

WebIDLpartial interface Element {
  undefined setPointerCapture (long pointerId);
  undefined releasePointerCapture (long pointerId);
  boolean hasPointerCapture (long pointerId);
};
setPointerCapture()

为由参数 pointerId 标识的指针在调用此方法的元素上设置指针捕获。对于该指针的后续事件,捕获目标会替代正常的命中测试结果,仿佛指针始终位于捕获目标之上,且这些事件MUST 在捕获释放前始终定向到此元素。为使此方法生效,指针MUST 处于其 活动按钮状态,否则将静默失败。当提供的参数不匹配任何 活动指针 时,抛出NotFoundErrorDOMException

releasePointerCapture()

为由参数 pointerId 标识的指针,从调用此方法的元素上释放指针捕获。该指针的后续事件将按照正常的命中测试机制(不在本规范范围内)确定事件目标。当提供的参数不匹配任何 活动指针 时,抛出NotFoundErrorDOMException

hasPointerCapture

指示调用此方法的元素是否对由参数 pointerId 标识的指针拥有指针捕获。具体而言,若 挂起的指针捕获目标覆盖 对应的 pointerId 被设置为调用此方法的元素,则返回 true,否则返回 false

在调用 setPointerCapture() 之后,该方法会立即返回 true,即便该元素尚未收到 gotpointercapture 事件。因此,它可用于在 隐式指针捕获 发生时(例如在 pointerdown 事件监听器内部)进行检测。

6. GlobalEventHandlers mixin 的扩展

下节描述对现有 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;
};
onpointerover
事件处理程序 IDL 属性,对应 pointerover 事件类型。
onpointerenter
事件处理程序 IDL 属性,对应 pointerenter 事件类型。
onpointerdown
事件处理程序 IDL 属性,对应 pointerdown 事件类型。
onpointermove
事件处理程序 IDL 属性,对应 pointermove 事件类型。
onpointerrawupdate
事件处理程序 IDL 属性,对应 pointerrawupdate 事件类型。
onpointerup
事件处理程序 IDL 属性,对应 pointerup 事件类型。
onpointercancel
事件处理程序 IDL 属性,对应 pointercancel 事件类型。
onpointerout
事件处理程序 IDL 属性,对应 pointerout 事件类型。
onpointerleave
事件处理程序 IDL 属性,对应 pointerleave 事件类型。
ongotpointercapture
事件处理程序 IDL 属性,对应 gotpointercapture 事件类型。
onlostpointercapture
事件处理程序 IDL 属性,对应 lostpointercapture 事件类型。

7. Navigator 接口的扩展

Navigator 接口在 [HTML] 中定义。本规范扩展 Navigator 接口以提供设备检测支持。

WebIDLpartial interface Navigator {
    readonly  attribute long maxTouchPoints;
};
maxTouchPoints

设备所支持的同时触摸接触点的最大数量。对于具有多个数字化仪的设备(例如多个触摸屏),该值MUST 取各个数字化仪所支持接触点最大值中的最大者。

例如,假设一台设备有 3 个触摸屏,分别支持 2、5 和 10 个同时触摸接触点。则 maxTouchPoints 的值应为 10

maxTouchPoints 大于 0 时,表明用户的设备能够支持触摸输入,但这并不必然意味着用户 使用触摸输入。作者还应谨慎考虑系统中可能存在的其他输入方式,如鼠标、笔或屏幕阅读器。
maxTouchPoints 常用于确保内容的交互模型可被当前硬件识别。可以为硬件能力较弱的用户提供相应的 UI 支持。在一些平台上,若无法得知触点的精确数量,则提供保证能被识别的最小数量。因此,实际被识别的触点数量可能会超过 maxTouchPoints 的值。

8. 声明直接操控行为

属性与默认操作 中所述,视口操控(平移与缩放)不能通过取消指针事件来抑制。作者必须使用 touch-action CSS 属性以声明性方式定义要允许/要抑制的行为。

虽然用于操控视口的问题通常仅限于触摸输入(用户手指既可与内容交互又可平移/缩放页面),但某些用户代理也可能对其他指针类型允许相同类型的(直接或间接)操控。例如,在手机/平板上,用户也可能使用触控笔滚动。尽管出于历史原因,本规范定义的 touch-action CSS 属性看起来仅与触摸输入有关,但它实际上适用于所有允许通过 直接操控 来平移和缩放的指针输入形式。

8.1 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 抑制一个指针事件流

一些用户代理为包含一系列离散手势、但被视为单一连续手势的行为实现了复杂手势。例如,在触摸屏上的“甩动滚动”:用户以快速手势开始平移文档,从触摸屏上抬起手指,文档继续以模拟惯性滚动。当文档仍在移动时,用户可能再次将手指放到触摸屏上执行另一次“甩动”以增加动量,或抵消当前滚动以减速、停止甚至反向。由于本规范未规范性定义手势与行为如何实现,是否在第二次触摸(在其被解释为第二次“甩动”或对当前滚动的抵消之前)触发指针事件,取决于用户代理。
touch-action 不会应用/级联到嵌入式浏览上下文。例如,即使在 <iframe> 上应用 touch-action,也不会对该 <iframe> 内部的平移和缩放直接操控行为产生任何影响。

8.2 确定支持的直接操控行为

当用户使用 直接操控 指针(如触摸屏上的手指或触控笔)与元素交互时,该输入的效果由 touch-action 属性的值,以及该元素及其祖先的默认直接操控行为共同决定,如下所示:

一些用户代理支持涉及多个并发指针(如多点触控)的平移和缩放交互。如何处理或关联多个并发指针的 touch-action 取值不在本规范范围内。

8.3 touch-action 取值详解

touch-action 属性涵盖与视口平移和缩放相关的直接操控行为。任何额外的用户代理行为(如文本选择/高亮、激活链接和表单控件)MUST NOT 受该 CSS 属性影响。

术语“panning”(平移)与“scrolling”(滚动)被视为同义(更准确地说,“平移”是使用直接操控输入的“滚动”)。用于触发平移/滚动的交互或手势的定义,或用于触发 autonone 取值行为的定义,不在本规范范围内。
auto
用户代理MAY 考虑在元素上开始的、与视口平移和缩放相关的任何被允许的直接操控行为。
none
在元素上开始的直接操控交互MUST NOT 触发与视口平移和缩放相关的行为。
pan-x
pan-left
pan-right
pan-y
pan-up
pan-down
用户代理MAY 仅将始于该元素的直接操控交互用于在所有列出的取值所指定的任意方向上开始的平移。一旦平移开始,用户可以反转方向,即使在反向方向上开始的平移是不被允许的。与之相对,当平移被限制在单一轴上(例如 pan-xpan-y),在平移过程中不可改变该轴。
manipulation
用户代理MAY 仅将始于该元素的直接操控交互用于平移和连续缩放(例如捏拉缩放),但MUST NOT 触发依赖于在规定时间内发生多次激活的其他相关行为(例如双击放大,或双击后按住进行单指缩放)。
touch-action 属性仅适用于同时支持 CSS widthheight 属性的元素(见 [CSS21])。此限制旨在便于用户代理对低延迟直接操控平移与缩放进行优化。对于默认不支持的元素(如作为非替换行内元素<span>),作者可以将 CSS display 设置为如 block 之类支持 widthheight 的值。未来规范可能将该 API 扩展至所有元素。

方向特定的 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: nonetouch-action: manipulation 将移除此延迟。注意,用于判定单击或双击手势的方法不在本规范范围内。
示例 7:禁止所有直接操控行为
<div style="touch-action: none;">
    该元素会接收所有本会导致平移或缩放的直接操控交互的指针事件。
</div>
示例 8:仅允许水平平移
<div style="touch-action: pan-x;">
    当未进行水平方向的平移时,该元素会接收指针事件。
</div>
示例 9:禁止平移和缩放直接操控行为的子区域
<div style="overflow: auto;">
    <div style="touch-action: none;">
        该元素会接收所有本会导致平移或缩放的直接操控交互的指针事件。
    </div>
    <div>
        在此元素上的直接操控交互 <MAY> 被用于操控父元素。
    </div>
</div>
示例 10: 中间父级禁止平移和缩放的直接操控行为
<div style="overflow: auto;">
    <div style="touch-action: pan-y;">
        <div style="touch-action: pan-x;">
            该元素会接收所有直接操控交互的指针事件,因为
            它仅允许水平平移,而位于其与可滚动元素之间的
            某个中间祖先仅允许垂直平移。
            因此,用户代理不会处理任何平移/缩放的直接操控行为。
        </div>
    </div>
</div>
示例 11: 中间父级限制允许的平移与缩放直接操控行为
<div style="overflow: auto;">
    <div style="touch-action: pan-y pan-left;">
        <div style="touch-action: pan-x;">
            当不向左平移时,该元素会接收指针事件。
        </div>
    </div>
</div>

9. 指针捕获 (Pointer capture)

9.1 简介 (Introduction)

本节为非规范性内容。

指针捕获允许某一特定指针的事件(包括任何兼容鼠标事件)被重定向到一个特定元素,而不是基于该指针位置的正常命中测试结果。它在自定义滑块控件场景中很有用(例如类似于 [HTML] 的 <input type="range"> 控件)。可以在滑块的滑块拇指元素上设置指针捕获,使用户即便让指针滑出拇指区域也能继续来回拖动。

自定义音量滑块
Figure 6 自定义滑块控件示例:通过拖动拇指元素来选取一个值。在拇指上发生 pointerdown 之后,即便指针偏离拇指,仍可利用指针捕获继续拖动拇指。

9.2 设置指针捕获 (Setting pointer capture)

通过调用 element.setPointerCapture(pointerId) 方法在一个类型为 elementElement 上设置指针捕获。 当该方法被调用时,用户代理MUST 依次执行以下步骤:

  1. 如果作为方法参数提供的 pointerId 不匹配任何活动指针,则抛出NotFoundErrorDOMException
  2. pointer 为由给定 pointerId 指定的活动指针
  3. 如果 element连接 (connected) [DOM],则抛出InvalidStateErrorDOMException
  4. 如果该方法在 element节点文档 [DOM] 拥有一个锁定元素([PointerLock] 的 pointerLockElement)时被调用,则抛出InvalidStateErrorDOMException
  5. 如果 pointer 不在活动按钮状态内,或 element节点文档 不是该 pointer活动文档,则终止这些步骤。
  6. 针对指定的 pointerId,将挂起的指针捕获目标覆盖 (pending pointer capture target override)设置为调用该方法的 Element
如果在前一次设置或释放指针捕获的调用仍处于挂起状态时又进行一次设置或释放(参见 处理挂起指针捕获 (process pending pointer capture)),第二次调用若成功会覆盖第一次调用,否则第一次调用继续生效。这个规则同样适用于在 隐式指针捕获pointerdown 监听器中尝试释放失败的情形。

9.3 释放指针捕获 (Releasing pointer capture)

通过调用 element.releasePointerCapture(pointerId) 方法在元素上显式释放指针捕获。调用该方法时,用户代理MUST 执行以下步骤:

  1. 如果作为方法参数提供的 pointerId 不匹配任何活动指针,且这些步骤的触发并非由于隐式释放指针捕获,则抛出NotFoundErrorDOMException
  2. 如果针对指定 pointerIdhasPointerCapture 为 false,则终止这些步骤。
  3. 针对指定的 pointerId,清除(若已设置)挂起的指针捕获目标覆盖
参见 设置指针捕获 一节中的注释。

9.4 隐式指针捕获 (Implicit pointer capture)

实现用于平移与缩放的直接操控交互的输入(例如触摸或触控笔)SHOULD 表现得仿佛在任何 pointerdown 监听器被调用前刚对目标元素调用了 setPointerCapture。可以使用 hasPointerCapture API(例如在 pointerdown 监听器中)来判断此行为是否发生。如果在下一次指针事件触发前未调用 releasePointerCapture,则会向该目标正常派发 gotpointercapture 事件表示捕获已激活。

这与 [PointerEvents] 有破坏性差异,但并不影响绝大多数现有内容。除了符合典型平台 UX 约定外,该隐式捕获设计还允许用户代理进行性能优化,使触摸移动事件无需在开发者显式选择的情况下进行命中测试(与现有主流原生及 Web 触摸输入 API 的性能特性保持一致)。
此外,用户代理可在特定 UI 控件(如范围输入控件)上为所有输入设备实现隐式指针捕获(允许交互过程中手指的部分移动偏离表单控件本身)。

9.5 指针捕获的隐式释放 (Implicit release of pointer capture)

在触发 pointeruppointercancel 事件之后,用户代理MUST 清除刚被派发的 pointeruppointercancel 事件所对应的 pointerId挂起的指针捕获目标覆盖,然后执行 处理挂起指针捕获 步骤以在必要时触发 lostpointercapture。 在执行完 处理挂起指针捕获 步骤后,如果该指针支持悬停,用户代理MUST 也要发送对应的边界事件以反映无捕获状态下指针当前位置。

指针捕获目标覆盖 (pointer capture target override) 不再连接 (connected) [DOM] 时,该 指针捕获目标覆盖SHOULD 设置为 document。

挂起的指针捕获目标覆盖 不再连接 (connected) [DOM] 时, 该 挂起的指针捕获目标覆盖 节点SHOULD 被清除。

前两段导致在捕获节点被移除后的下次 处理挂起指针捕获 (Process pending pointer capture) 中,于 document 上触发与被捕获指针对应的 lostpointercapture 事件。

当一个指针锁 [PointerLock] 成功应用于某元素时,如果任何元素已被设置为捕获或处于待捕获状态,用户代理MUST 按照调用 releasePointerCapture 方法的步骤执行。

10. 合并与预测事件 (Coalesced and predicted events)

本规范不定义用户代理应如何合并或预测指针移动数据,只规定访问这些信息的 API。

10.1 合并事件 (Coalesced events)

出于性能考虑,用户代理可能选择不在每次指针的某个可测属性 (如坐标、压力、切向压力、倾斜、旋转或接触几何)更新时都发送一个 pointermove 事件。取而代之,它们可能将多个变化合并到单个 pointermovepointerrawupdate 事件中。虽然此方式减少了用户代理MUST 进行的事件处理次数,但会自然降低指针位置跟踪的粒度与精确度,特别是快速且幅度大的移动。通过 getCoalescedEvents 方法,应用可以访问原始未合并的位置信息,从而更精细地处理指针运动数据。例如在绘图应用中,未合并事件可用于绘制更平滑的曲线,使其更接近指针真实移动轨迹。

曲线的特写,展示合并与未合并的点
Figure 7 绘图应用中一条曲线示例——仅使用来自 pointermove 事件的合并坐标(灰点)时,曲线明显呈现折角与锯齿;使用 getCoalescedEvents() 提供的更细粒度点(红圈)可获得更平滑的轨迹。

PointerEvent 具有关联的 合并事件列表 (coalesced events list)(零个或多个 PointerEvent)。对于可信的 pointermovepointerrawupdate 事件,该列表是所有被合并进此事件的 PointerEvent 序列。可信的“父” pointermovepointerrawupdate 事件表示这些合并事件的累计,且可能包含额外处理(例如对齐显示刷新率)。因此这些事件的合并事件列表总是至少包含一个事件。对所有其他可信事件类型,该列表为空。非可信事件的 合并事件列表初始化为构造函数传入的值。

由于可信父事件是合并事件的汇总,开发者只需处理父事件或全部合并事件,通常不需要同时处理两者。
当包含 合并事件列表 的可信事件在 JavaScript 中被重新派发时,事件派发算法会将该事件的 isTrusted 置为 false,但其 合并事件列表 中事件的该标志保持原始的 true 值不变。

可信事件的合并事件列表中的事件具有以下特征:

Example 12: 使用合并事件列表的基础 Canvas 绘图应用
<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>
PointerEvent 的各属性将以最佳方式初始化以代表合并事件列表中的事件。用户代理应如何具体执行不在本规范范围内。

所有已派发事件的顺序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 个合并事件

10.2 预测事件 (Predicted events)

一些用户代理具有内置算法,可在连续确认的指针移动之后基于当前手势的先前事件及其速度/轨迹对未来指针移动位置进行预测。应用可以使用 getPredictedEvents 方法获取这些信息,以“提前绘制”到预测位置,从而降低感知延迟,并在实际点到达后舍弃这些预测点。

使用合并点绘制的线条,并显示预测的未来点
Figure 8 绘图应用中的一条线(从左下到右上手势的结果),使用来自 pointermove 事件的合并坐标,并显示用户代理预测的未来点(灰圈)。

PointerEvent 具有关联的 预测事件列表 (predicted events list)(零个或多个 PointerEvent)。对于可信的 pointermove 事件,它是用户代理预测将要跟随该事件的 PointerEvent 序列。其它可信事件类型为空。非可信事件的 预测事件列表 初始化为构造函数传入的值。

虽然 pointerrawupdate 事件可能拥有非空的 合并事件列表,其 预测事件列表 出于性能原因通常为空。

当包含 预测事件列表 的可信事件在 JavaScript 中重新派发时,事件派发算法将该事件的 isTrusted 设为 false,但其 预测事件列表 中事件的该标志保持原始 true

列表中事件的数量以及它们距离当前时间戳的远近由用户代理及其预测算法决定。

可信事件的预测事件列表中的事件具有以下特征:

作者应仅在下一个指针事件派发前视预测事件为有效预测。取决于用户代理预测的未来长度,常规指针事件可能在某些预测事件时间戳之前更早派发。

Example 13: 使用合并事件与预测事件绘制的概念性方案

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);
    }
});

10.3 填充并维护合并与预测事件列表 (Populating and maintaining the coalesced and predicted events lists)

当一个可信的 PointerEvent 被创建时,用户代理SHOULD 对其 合并事件列表预测事件列表中的每个事件执行以下步骤:

  1. 将该事件的 pointerIdpointerTypeisPrimary 以及 isTrusted 设为与“父”指针事件相同。
  2. 将该事件的 cancelablebubbles 设为 false(这些事件永远不会单独派发)。
  3. 将该事件的 合并事件列表预测事件列表 置为空。
  4. 将所有其他属性初始化为 PointerEvent 默认值。

当一个可信的 PointerEventtarget 被更改时,用户代理SHOULD 针对其 合并事件列表预测事件列表中的每个事件:

  1. 将该事件的 target 设置为与“父”指针事件的 target 相同。

11. 与鼠标事件的兼容映射 (Compatibility mapping with mouse events)

当今绝大多数现存网页内容只编写了 Mouse Events。下文描述一个算法,用户代理 MAY 将通用指针输入映射为鼠标事件,以与这些内容保持兼容。

与鼠标事件的兼容映射是本规范的一个 OPTIONAL 特性。鼓励用户代理支持该特性以获得与现有遗留内容的最佳兼容性。

总体上,兼容鼠标事件旨在与其对应的指针事件“交错”出现。不过这一具体顺序并非强制,支持兼容鼠标事件的用户代理 MAY 延迟或成组派发鼠标事件,只要它们的相对顺序保持一致。

特别是在触摸屏输入的情况下,用户代理 MAY 为手势识别应用额外启发式(除非作者通过 touch-action 明确抑制)。在从一个 pointerdown 到一个 pointerup 的事件序列中,手势识别可能需要等到该 pointerup 事件才能判断是否检测或忽略某个手势。因此,如果用户代理判定交互并非某一特定手势,则整段序列的兼容鼠标事件可能在最后一个 pointerup 事件后再一起派发。用户代理执行手势识别的这些细节不在本规范中定义,并且不同实现之间可能存在差异。

无论是否支持兼容鼠标事件,用户代理 MUST 始终支持 clickauxclickcontextmenu 事件,因为这些事件是 PointerEvent 类型,因此不是 兼容鼠标事件。在指针事件期间调用 preventDefault MUST NOT 影响 clickauxclickcontextmenu 是否被触发。

某些高层事件(例如 contextmenufocusblur)与指针事件的相对顺序未定义,且在不同用户代理之间有所不同。例如,在一些用户代理中,contextmenu 通常跟随一个 pointerup,而在另一些中它可能先于一个 pointeruppointercancel,并且在某些情况它可能在没有任何对应指针事件的情况下触发(例如键盘交互的结果)。

此外,用户代理可使用自身启发式来决定是否触发 clickauxclickcontextmenu。某些用户代理在存在其他(非主)同类型指针或存在不同类型的其他主指针时可能选择不触发这些事件。用户代理可能判定某个动作不是“干净”的轻点、点击或长按(例如在触摸屏上手指接触期间移动过多)而决定不触发 clickauxclickcontextmenu。这些用户代理行为方面不在本规范定义范围内,且实现间可能不同。

除非另有说明,任何映射后的鼠标事件的目标 SHOULD 与对应指针事件的目标相同,除非该目标已不再参与其 ownerDocument 的树。在这种情况下,鼠标事件应在原始目标被移除时仍在树中的最近祖先节点上触发,即为该鼠标事件基于新目标节点构建新的事件路径。

作者可以通过取消 pointerdown 事件来阻止某些兼容鼠标事件的产生。

只有在指针按下时才能阻止鼠标事件。悬停指针(例如没有按钮按下的鼠标)其鼠标事件无法被阻止。

mouseovermouseoutmouseentermouseleave 事件永远不会被阻止(即便指针处于按下状态)。

当指针事件的 EventListener 被设置为 passive [DOM] 时,兼容鼠标事件不可被阻止。

11.1 跟踪传统鼠标指针的有效位置 (Tracking the effective position of the legacy mouse pointer)

虽然只有主指针能产生兼容鼠标事件,但多个主指针可以同时处于活动状态,各自产生自身的兼容鼠标事件。为兼容依赖 MouseEvents 的脚本,鼠标过渡事件(mouseovermouseoutmouseentermouseleaveSHOULD 模拟单个传统鼠标输入的移动。这意味着每个事件目标的进入/退出状态应根据 [UIEVENTS] 规范有效。用户代理 SHOULD 通过在文档中维护传统鼠标指针的有效位置 (effective position of the legacy mouse pointer) 来实现。

在触发一个 pointerdownpointeruppointermove 事件,或在 window 上发生一个 pointerleave 事件之前,用户代理 SHOULD 执行以下步骤:

  1. T 为将要派发的 pointerdownpointeruppointermove 事件的目标。对于 pointerleave 事件,取消设置 T
  2. 如果 T 与当前 传统鼠标指针的有效位置 都未设置或两者相等,终止这些步骤。
  3. 根据 [UIEVENTS] 派发从当前 传统鼠标指针的有效位置T 的鼠标移动所需的 mouseovermouseoutmouseentermouseleave 事件。若当前 传统鼠标指针的有效位置T 为未设置,将其视为窗口外的鼠标位置。
  4. 传统鼠标指针的有效位置 设置为 T

传统鼠标指针的有效位置 体现了:我们无法总是将指针过渡事件(pointeroverpointeroutpointerenterpointerleave)直接映射到对应的传统鼠标过渡事件(mouseovermouseoutmouseentermouseleave)。下动画展示一个场景:用户代理需要派发比指针过渡事件更多的传统鼠标过渡事件,以便使用单个传统鼠标输入协调两个主指针。

Figure 9 同时出现的鼠标指针(白色光标)与触摸指针(白色“手”光标)导致单个传统鼠标输入(橙色光标)在两个指针之间移动。

在该动画中,注意鼠标点击与触摸轻点之间的时间段。Button 1 未收到 pointerout 事件(因为“真实”鼠标指针在此期间没有离开按钮矩形),但当 传统鼠标指针的有效位置 在触摸轻点时移动到 Button 2,Button 1 收到一个 mouseout 事件。类似地,在触摸轻点与鼠标即将离开 Button 1 之间的时间段里,Button 1 未收到 pointerover 事件,但当 传统鼠标指针的有效位置 移回 Button 1 内部时,Button 1 收到一个 mouseover 事件。

11.2 支持悬停设备的映射 (Mapping for devices that support hover)

每当用户代理要为支持悬停的设备派发一个指针事件时,它 SHOULD 执行以下步骤:

  1. 如果待派发指针事件的 isPrimary 属性为 false,则派发该指针事件并终止这些步骤。
  2. 如果待派发指针事件是 pointerdownpointeruppointermove 事件,或在 window 上的 pointerleave 事件,则按 跟踪传统鼠标指针的有效位置 的描述派发兼容鼠标过渡事件。
  3. 派发该指针事件。
  4. 如果刚派发的是 pointerdown 且事件被 canceled,则为该 pointerType 设置 PREVENT MOUSE EVENT 标志。
  5. 如果该 pointerType 没有设置 PREVENT MOUSE EVENT 标志,并且刚派发的指针事件是:
  6. 如果刚派发的指针事件是 pointeruppointercancel,则清除该 pointerTypePREVENT MOUSE EVENT 标志。

11.3 不支持悬停设备的映射 (Mapping for devices that do not support hover)

某些设备(如多数触摸屏)不支持在未激活状态悬停一个坐标(或一组坐标)。大量基于鼠标事件编写的现有内容假定事件由鼠标产生,因此通常成立以下特性:

“悬停”有时用于在为鼠标设计的内容中切换 UI 元素的可见性(例如“悬停菜单”)。此类内容往往与 不支持悬停的设备 不兼容。本规范未定义针对该场景的映射或行为,将在未来版本中考虑。

这需要用户代理为此类输入设备提供不同的映射。每当用户代理要为一个 不支持悬停 的设备派发指针事件时,它 SHOULD 执行以下步骤:

  1. 如果待派发指针事件的 isPrimary 属性为 false,则派发指针事件并终止这些步骤。
  2. 如果待派发指针事件是 pointerover 且该指针的 pointerdown 事件尚未派发,则触发一个 mousemove 事件(与遗留鼠标特定代码兼容)。
  3. 如果待派发指针事件是 pointerdownpointeruppointermove 事件,或在 window 上的 pointerleave 事件,则按 跟踪传统鼠标指针的有效位置 描述派发兼容鼠标过渡事件。
  4. 派发该指针事件。
  5. 如果刚派发的是 pointerdown 且事件被 canceled,则为该 pointerType 设置 PREVENT MOUSE EVENT 标志。
  6. 如果该 pointerType 没有设置 PREVENT MOUSE EVENT 标志,并且刚派发的指针事件是:
  7. 如果刚派发的指针事件是 pointeruppointercancel,则清除该 pointerTypePREVENT MOUSE EVENT 标志。

如果用户代理同时支持 Touch Events(定义于 [TOUCH-EVENTS])与 Pointer Events,则 用户代理 MUST NOT 同时生成本节描述的兼容鼠标事件与 [TOUCH-EVENTS] 中概述的回退鼠标事件

使用一个不支持悬停的主指针(例如触摸屏单指)激活元素(click)通常会产生如下事件序列:

  1. mousemove
  2. pointerover
  3. pointerenter
  4. mouseover
  5. mouseenter
  6. pointerdown
  7. mousedown
  8. 零个或多个 pointermovemousemove 事件,取决于指针移动
  9. pointerup
  10. mouseup
  11. pointerout
  12. pointerleave
  13. mouseout
  14. mouseleave
  15. click

然而,如果在此交互期间 pointerdown 事件被 canceled,则事件序列会变为:

  1. mousemove
  2. pointerover
  3. pointerenter
  4. mouseover
  5. mouseenter
  6. pointerdown
  7. 零个或多个 pointermove 事件,取决于指针移动
  8. pointerup
  9. pointerout
  10. pointerleave
  11. mouseout
  12. mouseleave
  13. click

12. 安全与隐私注意事项 (Security and privacy considerations)

本附录讨论 Pointer Events 实现的安全与隐私注意事项。讨论范围仅限于直接由本规范定义的事件模型、API 与事件的实现所引发的安全与隐私问题。

本规范中定义的许多事件类型是响应用户操作而派发的。这允许恶意事件监听器获取用户通常认为是机密的信息,例如用户在页面上交互时鼠标 / 触控笔 / 手指的精确路径 / 移动。

指针事件包含额外信息(如果用户设备支持),例如笔输入的角度或倾斜、接触面的几何形状以及施加在触控笔或触摸屏上的压力。有关角度、倾斜、几何和压力的信息与用户设备上的传感器直接相关,这意味着本规范允许一个源访问这些传感器。

这些传感器数据以及确定使用的输入机制类型(鼠标、触摸、笔)的能力,可能被用来推断用户或设备及环境的特征。这些推断出的特征以及任何设备 / 环境信息本身可能是敏感的——例如,它们可能允许恶意站点进一步推断用户是否使用了辅助技术。这些信息也可能被用来构建用户档案以及尝试“指纹”并跟踪特定用户。

作为缓解,用户代理可考虑提供能力让用户禁用对特定传感器数据(例如角度、倾斜、压力)的访问,及/或只在用户明确选择后才提供这些数据。

本规范定义了作者访问“预测事件”的方法。规范本身并未定义用户代理应使用的预测算法。规范作者设想算法仅依赖与当前用户手势相关的前序指针事件。用户代理有责任确保其具体的预测算法实现不依赖任何额外数据——例如跨不同站点的完整交互历史——这些数据可能泄露用户的敏感信息或被用于“指纹”与跟踪。

除上述注意事项外,工作组认为本规范:

13. 术语表 (Glossary)

本节为非规范性内容。

active buttons state(活动按钮状态)
指针的 buttons 属性为非零值的状态。鼠标:至少一个按钮被按下。触摸:与数字化仪发生物理接触。笔:与数字化仪物理接触,或在悬停时至少有一个按钮被按下。
active document(活动文档)
对于每个 活动指针,该指针最近一次事件的接收文档。
active pointer(活动指针)
任何触摸接触、笔/触控笔、鼠标光标或其他能产生事件的指针。如果某个具有唯一 pointerId 的指针仍可能在文档内产生其他事件,则该指针仍被视为活动。示例:
  • 连接到设备的鼠标始终是活动的。
  • 屏幕上的触摸接触被视为活动。
  • 若触摸接触或笔/触控笔被抬离数字化仪检测范围,则不再视为活动。
在某些平台上,活动指针集合包括设备的所有指针输入,即使那些未以用户代理为目标(例如目标为其他应用的输入)。
canceled event(被取消的事件)
其默认操作被 preventDefault()、在事件处理器中返回 false 或其他 [UIEVENTS] 与 [HTML] 定义手段阻止的事件。
contact geometry(接触几何)
输入(最常见为触摸)在数字化仪上的外接矩形。典型用于指针分辨率粗于单像素的设备。有些设备完全不报告此数据。
digitizer(数字化仪)
一种输入感应设备,其表面可检测接触以及/或近距离的输入。最常见为感应触摸接触或笔/触控笔输入的表面。
direct manipulation(直接操控)
某些用户代理(例如运行在触摸屏设备上的浏览器)实现一种“直接操控”比喻:指针不仅与控件交互,还被用来直接平移或缩放当前页面,提供直接物理接触的错觉。例:触摸设备用户通常可使用手指或触控笔“抓住”页面并通过移动指针来平移页面,直接操控页面。与之对比,常规桌面/笔记本上的鼠标指针通常通过滚动条平移,而不是“拖动”页面。
在某些情况下,触摸板(如笔记本上的)允许用户通过“拖动”触摸板来滚动。然而这通常是触摸板生成“伪”鼠标滚轮事件实现的,因此不计为直接操控。
hit test(命中测试)
用户代理确定指针事件目标元素的过程。通常通过考虑指针位置以及文档在屏幕媒介上的视觉布局。
measurable properties(可测属性)

可测属性表示与连续指针传感器数据相关的值,这些值以实数或来自大域的整数表达。对于指针事件,widthheightpressuretangentialPressuretiltXtiltYtwistaltitudeAngleazimuthAngle 以及 [UIEVENTS] 鼠标事件模型中的 screenXscreenYclientXclientY 都是可测属性。

相比之下,pointerIdpointerTypeisPrimary,以及 [UIEVENTS] 鼠标事件模型中的 buttonbuttonsctrlKeyshiftKeyaltKeymetaKey 不被视为可测属性,因为它们不涉及传感器数据。

pointer(指针)
对可在屏幕上定位特定坐标(或坐标集合)的输入设备的与硬件无关表示,如鼠标、笔或触摸接触。

A. 致谢

非常感谢众多人士提出的建议与推荐,其中一些已纳入本文档。工作组主席谨此感谢以下历任与现任的组员和参与者的贡献: 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。

B. 修订历史

本节为非规范性内容。

以下是相较于 [PointerEvents3] 规范,本规范各版本发布之间实质性与主要编辑性变更的说明性摘要。 参见本规范编辑草案的完整修订历史

C. IDL 索引

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;
};

D. 参考文献

D.1 规范性参考文献

[CSS-OVERFLOW-3]
CSS Overflow Module Level 3。Elika Etemad;Florian Rivoal。W3C。2025 年 10 月 7 日。W3C 工作草案。URL:https://www.w3.org/TR/css-overflow-3/
[CSS21]
Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification。Bert Bos;Tantek Çelik;Ian Hickson;Håkon Wium Lie。W3C。2011 年 6 月 7 日。W3C 推荐标准。URL:https://www.w3.org/TR/CSS2/
[CSSOM-VIEW]
CSSOM View Module。Simon Fraser; Emilio Cobos Álvarez。W3C。2025 年 9 月 16 日。W3C 工作草案。URL:https://www.w3.org/TR/cssom-view-1/
[DOM]
DOM Standard。Anne van Kesteren。WHATWG。 Living Standard。URL:https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification。 Ecma International。URL:https://tc39.es/ecma262/multipage/
[HTML]
HTML Standard。Anne van Kesteren; Domenic Denicola;Dominic Farolino;Ian Hickson;Philip Jägenstedt;Simon Pieters。WHATWG。Living Standard。URL:https://html.spec.whatwg.org/multipage/
[infra]
Infra Standard。Anne van Kesteren;Domenic Denicola。WHATWG。Living Standard。URL:https://infra.spec.whatwg.org/
[PointerLock]
Pointer Lock。Vincent Scheib。W3C。2016 年 10 月 27 日。W3C 推荐标准。URL:https://www.w3.org/TR/pointerlock/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels。S. Bradner。IETF。1997 年 3 月。最佳现行实践。URL:https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words。B. Leiba。IETF。2017 年 5 月。最佳现行实践。URL:https://www.rfc-editor.org/rfc/rfc8174
[TOUCH-EVENTS]
Touch Events。Doug Schepers;Sangwhan Moon;Matt Brubeck;Arthur Barstow。W3C。2013 年 10 月 10 日。W3C 推荐标准。URL:https://www.w3.org/TR/touch-events/
[UIEVENTS]
UI Events。Gary Kacmarcik;Travis Leithead。W3C。2024 年 9 月 7 日。W3C 工作草案。URL:https://www.w3.org/TR/uievents/
[WEBIDL]
Web IDL Standard。Edgar Chen;Timothy Gu。 WHATWG。Living Standard。URL:https://webidl.spec.whatwg.org/

D.2 资料性参考文献

[COMPAT]
Compatibility Standard。Mike Taylor。 WHATWG。Living Standard。URL:https://compat.spec.whatwg.org/
[PointerEvents]
Pointer Events。Jacob Rossi;Matt Brubeck。W3C。2019 年 4 月 4 日。W3C 推荐标准。URL:https://www.w3.org/TR/pointerevents/
[PointerEvents3]
Pointer Events。Patrick Lauke; Robert Flack。W3C。2025 年 5 月 1 日。CRD。URL:https://www.w3.org/TR/pointerevents3/
[WCAG22]
Web Content Accessibility Guidelines (WCAG) 2.2。Michael Cooper;Andrew Kirkpatrick;Alastair Campbell;Rachael Bradley Montgomery;Charles Adams。W3C。2024 年 12 月 12 日。W3C 推荐标准。URL:https://www.w3.org/TR/WCAG22/