游戏手柄

W3C 工作草案

关于此文档的更多细节
此版本
https://w3org.cn/TR/2025/WD-gamepad-20250710/
最新发布版本
https://w3org.cn/TR/gamepad/
最新编辑草案
https://w3c.github.io/gamepad/
历史
https://w3org.cn/standards/history/gamepad/
提交历史
测试套件
https://wpt.live/gamepad/
实现报告
https://wpt.fyi/results/gamepad
编辑
Steve Agoston (Sony)
Matt Reynolds (Google)
前任编辑
James Hollyer (Google)
Brandon Jones (Google)
Scott Graham (Google)
Ted Mielczarek (Mozilla)
反馈
GitHub w3c/gamepad (合并请求, 新建议题, 开放议题)
浏览器支持
caniuse.com

摘要

Gamepad 规范定义了一个表示游戏手柄设备的低级接口。

本文档状态

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

这是正在进行中的工作。

本文档由 Web 应用工作组 作为工作草案发布,遵循 推荐标准路径

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

这是一份草案文件,可能随时被其他文件更新、替换或废弃。将其作为进展中的工作以外的引用是不恰当的。

本文档由在一个受 W3C 专利政策 约束下运营的组织所编写。W3C 维护了一份 与该工作组交付成果相关的专利披露公开列表;该页面还包含披露专利的说明。任何知晓其认为包含 必要权利要求 的专利的个人,必须按照 W3C 专利政策第 6 节 的规定披露相关信息。

本文档受 2023 年 11 月 3 日 W3C 流程文档 管辖。

1. 简介

本节是非规范性的。

一些 用户代理 连接了游戏手柄设备。这些设备在游戏应用及“10英尺”用户界面(演示文稿、媒体查看器)中是非常理想且适用的输入设备。

目前,使用游戏手柄作为输入的唯一方式是模拟鼠标或键盘事件,但这会丢失信息,并且需要用户代理之外的额外软件来完成模拟。

与此同时,原生应用能够通过系统 API 访问这些设备。

Gamepad API 通过指定允许 Web 应用直接操作游戏手柄数据的接口,为该问题提供了一个解决方案。

2. 范围

如果以完全通用的方式处理与控制游戏的外部设备进行接口对接,其工作量可能会变得巨大且难以处理。在本规范中,我们明确选择缩小范围,提供一个可被广泛实现且具有普遍实用性的功能子集。

具体而言,我们选择仅支持支持游戏手柄所需的功能。支持游戏手柄需要两种输入类型:按钮和轴。按钮和轴均报告为模拟值,按钮范围为 [0 .. 1],轴范围为 [-1 .. 1]。

虽然主要目标是支持游戏手柄设备,但支持这两种类型的模拟输入也允许支持当前游戏系统中常见的其他类似设备,包括操纵杆、方向盘、踏板和加速度计。因此,“gamepad”(游戏手柄)这个名称是示例性的,而不是试图成为本规范所涉及的整套设备的总称。

我们明确排除了对某些可能在特定游戏场景中使用且更为复杂的设备的支持,包括那些涉及运动传感、深度传感、视频分析、手势识别等的设备。

3. 模型

一个 游戏手柄 是输入控件和输出控件的集合。一个 输入控件 拥有随时间更新的 输入值 集合。输入控件包括 游戏手柄 的按钮、触发器、操纵杆、拇指摇杆和触摸表面。一个 输出控件 是一种通过改变 游戏手柄 的行为来向用户提供反馈的特性。输出控件包括 游戏手柄 的触觉执行器。如果 用户代理 可以读取其 输入控件 的当前状态,则称该 游戏手柄可用的。不可用的 游戏手柄 被称为 不可用的。在 游戏手柄 可用 期间,其 输入控件输出控件 不能改变。

用户代理 负责以下工作:

游戏手柄 具有一个 游戏手柄标识字符串,这是一个标识 游戏手柄 品牌或样式的易读字符串。其内容由 用户代理 决定。

3.1 输入控件布局

游戏手柄 可能具有一个 输入控件布局,描述了 游戏手柄 上每个 输入控件 的位置、方向和类型。用户代理 负责识别 游戏手柄 何时 符合标准布局,这意味着该 游戏手柄 具有一种 输入控件布局,使其能够与符合相同标准布局的其他 游戏手柄 互换使用。用户代理 应该 认为如果布局的 输入控件 与标准布局中所述的 输入控件 具有大致相同的位置和方向,则该布局符合标准布局。

用户代理 通常无法直接检查 游戏手柄输入控件布局,并且 可以 使用启发式方法来确定布局。用户代理 在决定 游戏手柄 是否 符合标准布局应该 考虑设备标识符。如果系统为每个 输入控件 分配了标签,且这些标签暗示了特定的布局,则 用户代理 应该 认为该 游戏手柄 具有该布局。当存在标准模型和具有相同 输入控件 的辅助功能模型时,用户代理 应该 认为辅助功能模型具有与标准模型相同的 输入控件布局

辅助功能游戏手柄模型是指制造商旨在提供可替代具有标准布局的游戏手柄的游戏手柄。例如,Xbox 无障碍控制器 (Xbox Adaptive Controller) 和 PlayStation 无障碍控制器 (PlayStation Access Controller) 是辅助功能游戏手柄模型。Xbox 无线控制器 (Xbox Wireless Controller) 和 DualSense 是相应的标准模型。

3.2 输入值

每个输入控件都有一个或多个关联的 输入值,这些是表示控件当前状态的数值。输入值 可以随时更新。用户代理 负责检测 输入值 何时更新,并 应该 尽量减少更新与读取更新后的值之间的延迟。

读取 输入值 会返回其 逻辑值,这是对当前状态的非缩放数值表示。输入值 还具有 逻辑最小值逻辑最大值,它们定义了范围内的最小和最大 逻辑值

输入值 可能具有关联的 HID 使用标识符,这是一个标识输入值所代表数据类型的 32 位值。HID 使用情况不能精确描述 输入控件布局,但按照惯例,许多具有相似布局的 游戏手柄 使用相似的使用情况。用户代理 在决定 输入控件布局应该 依赖围绕 HID 使用标识符 的惯例。

3.3

游戏手柄 可能具有 输入。 是一个 输入值,表示控件相对于参考位置的当前位移。

游戏手柄 拥有一个 轴列表,这是一个包含 游戏手柄 的所有 输入的 列表,其顺序由 用户代理 确定。

输入控件 可能设计为在用户停止与 输入控件 交互时自动将 返回到中心位置。如果是这样,则该 具有一个 首选轴状态。具有 首选轴状态 可能还具有一个额外的 输入值,即 中心位置值,这是 居中时的 逻辑值

3.4 按钮

游戏手柄 可能具有 按钮 输入。按钮 是一个可以按下以激活的 输入控件游戏手柄 拥有一个 按钮列表,这是一个包含 游戏手柄 的所有 按钮 输入的 列表,其顺序由 用户代理 确定。

输入控件 可能设计为在用户停止与 输入控件 交互时自动将 按钮 返回到未按下状态。如果是这样,则该 按钮 具有一个 首选按钮状态

按钮 可能具有一个数字开关来指示 按钮 何时被激活。如果是这样,则该 按钮 具有一个额外的 输入值,即 数字按钮值,当 按钮 被激活时该值为 true,否则为 false

按钮 可能具有一个模拟传感器,使 按钮 能够报告其被激活的程度。如果是这样,该 按钮 具有:

按钮 可能具有检测触摸的能力。如果是这样,则该 按钮 具有一个额外的 输入值,即 按钮触摸值,当 按钮 被触摸时该值为 true,否则为 false

3.5 触摸表面

游戏手柄 可能具有 触摸表面触摸表面 是一种输入控件,提供表示接触点的 2D 位置数据。游戏手柄 拥有一个 触摸表面列表,这是一个包含 游戏手柄触摸表面列表。列表的排序方式使得越靠近 游戏手柄 左侧的 触摸表面 在列表中出现得越靠前。

触摸表面 具有一个 活动触摸点列表 输入值,这是一份零个或多个 触摸点列表,代表当前传感器检测到的接触点。触摸点 表示在特定时间点的单个接触点。触摸点 具有 触摸 X 坐标触摸 Y 坐标,表示 触摸表面 坐标系中的位置。如果 触摸表面 位于 游戏手柄 的顶部、底部、正面或背面,则 触摸 X 坐标 沿左右轴测量,否则沿顶底轴测量。触摸 Y 坐标 沿垂直轴测量。

如果 触摸表面 位于 游戏手柄 的左侧或右侧,则其任何维度都不会与水平轴对齐。

触摸表面 可能具有表面维度 输入值表面宽度表面高度 输入值触摸表面 的维度,单位与 触摸 X 坐标触摸 Y 坐标 相同。触摸表面 要么同时具有这两个维度值,要么都不具备。

触摸点 可能是新的接触点,也可能是早期接触的延续。如果 用户代理 识别出它是早期 GamepadTouch 所代表的 触摸点 的延续,则该 触摸点 被认为是 现有活动触摸点的一部分。对于 现有活动触摸点的一部分触摸点,其 活动触摸点 ID 是早期 GamepadTouchtouchId

3.6 输出控件

游戏手柄 可能具有 触觉执行器触觉执行器 是一种输出控件,能够以用户可以感知的方式移动 游戏手柄触觉执行器 可用于生成提供用户反馈的 触觉效果。来自多个执行器的振动结合起来生成更复杂的效果。用户代理 负责命令 触觉执行器可用游戏手柄 上播放和停止 触觉效果

游戏手柄 可能具有一个 振动执行器,这是一个能够播放 触觉效果 来振动整个 游戏手柄触觉执行器

触觉执行器 拥有一个 支持的效果类型 列表,包含一个或多个 GamepadHapticEffectType 值,这些值在 游戏手柄 可用 期间不能更改。

4. Gamepad 接口

该接口定义了一个独立的游戏手柄设备。

WebIDL[Exposed=Window]
interface Gamepad {
  readonly attribute DOMString id;
  readonly attribute long index;
  readonly attribute boolean connected;
  readonly attribute DOMHighResTimeStamp timestamp;
  readonly attribute GamepadMappingType mapping;
  readonly attribute FrozenArray<double> axes;
  readonly attribute FrozenArray<GamepadButton> buttons;
  readonly attribute FrozenArray<GamepadTouch> touches;
  [SameObject] readonly attribute GamepadHapticActuator vibrationActuator;
};

用于与系统通信的算法通常异步完成,在 游戏手柄任务源 上排队工作。

Gamepad 的实例在创建时带有下表中描述的内部槽位:

内部槽位 初始值 描述(非规范性)
[[connected]] false 指示设备已连接到系统的标志
[[timestamp]] undefined Gamepad 的数据最后一次更新的时间
[[axes]] 一个空 序列 表示该设备暴露的轴的当前状态的 double 值序列
[[buttons]] 一个空 序列 表示该设备暴露的按钮的当前状态的 GamepadButton 对象序列
[[exposed]] false 指示 Gamepad 对象是否已暴露给脚本的标志
[[axisMapping]] 一个空 有序映射 从未映射轴索引到 axes 数组中索引的映射
[[axisMinimums]] 一个空 列表 包含每个轴的逻辑最小值的 列表
[[axisMaximums]] 一个空 列表 一个包含每个轴的逻辑最大值的 列表
[[buttonMapping]] 一个空 有序映射 从未映射按钮索引到 buttons 数组中索引的映射
[[buttonMinimums]] 一个空 列表 包含每个按钮的逻辑最小值的 列表
[[buttonMaximums]] 一个空 列表 包含每个按钮的逻辑最大值的 列表
[[touches]] 一个空 列表 保存用户生成的触摸列表(如果有)。如果游戏手柄不支持触摸表面,则列表保持为空。
[[nextTouchId]] 0 用于下一个传入触摸的 touchId 值。
[[vibrationActuator]] undefined 一个能够生成振动整个游戏手柄的触觉效果的 GamepadHapticActuator 对象
id 属性

游戏手柄的识别字符串。该字符串标识已连接的游戏手柄设备的品牌或样式。

id 字符串的确切格式未指定。建议 用户代理 选择一个能识别产品但不能唯一识别设备的字符串。例如,USB 游戏手柄可以通过其 idVendoridProduct 值来识别。序列号或蓝牙设备地址等唯一标识符 绝不能 包含在 id 字符串中。

index 属性
游戏手柄在 Navigator 中的索引。当多个游戏手柄连接到 用户代理 时,索引 必须 从零开始按先到先得的原则分配。如果游戏手柄断开连接,之前分配的索引 绝不能 重新分配给持续连接的游戏手柄。然而,如果一个游戏手柄断开连接,随后连接了相同或不同的游戏手柄,则 必须 重用最低的先前使用过的索引。
connected 属性

指示此对象表示的物理设备是否仍连接到系统。当游戏手柄变得不可用时(无论是物理断开、关机还是以其他方式不可用),connected 属性 必须 设置为 false

connected 获取器的步骤为:

  1. 返回 this.[[connected]]
timestamp 属性

timestamp 允许作者确定此游戏手柄的 axesbuttons 属性最后一次更新的时间。每当系统 收到来自设备的新按钮或轴输入值 时,该值 必须 设置为 当前高分辨率时间。如果未从硬件接收到任何数据,timestamp 必须Gamepad 首次对脚本可用时的 当前高分辨率时间

Warning

用户代理 应该timestamp 属性的最小分辨率设置为 5 微秒,遵循 [HR-TIME] 的时钟分辨率建议。

timestamp 获取器的步骤为:

  1. 返回 this.[[timestamp]]
mapping 属性

该设备正在使用的映射。如果 用户代理 了解设备的布局,则它 应该 通过将 mapping 设置为相应的 GamepadMappingType 值来指示映射正在使用中。

要为游戏手柄设备 选择映射,请执行以下步骤:

  1. 如果游戏手柄设备的按钮和轴布局符合 标准游戏手柄 布局,则返回 "standard"。
  2. 返回 ""。
axes 属性

游戏手柄所有轴的值数组。所有轴值 必须 线性归一化到 [-1 .. 1] 范围。如果控制器垂直于地面且方向摇杆指向向上,-1 应该 对应于“向前”或“左”,1 应该 对应于“向后”或“右”。从 2D 输入设备获取的轴 应该 在轴数组中彼此相邻,X 在前,Y 在后。建议 轴按重要性递减顺序出现,使得元素 0 和 1 通常代表方向摇杆的 X 和 Y 轴。在 用户代理 需要返回不同值(或不同顺序的值)之前,必须 返回相同的对象。

axes 获取器的步骤为:

  1. 返回 this.[[axes]]
buttons 属性

游戏手柄所有按钮的按钮状态数组。建议 按钮按重要性递减顺序出现,使得主按钮、次要按钮、第三按钮等分别作为按钮数组的元素 0、1、2...。在 用户代理 需要返回不同值(或不同顺序的值)之前,必须 返回相同的对象。

buttons 获取器的步骤为:

  1. 返回 this.[[buttons]]
touches 属性

由所有触摸表面生成的 GamepadTouch 对象的 列表

touches 获取器的步骤为:

  1. 返回 this.[[touches]]
vibrationActuator 属性

表示设备主要振动执行器的 GamepadHapticActuator 对象。

vibrationActuator 获取器的步骤为:

  1. 返回 this.[[vibrationActuator]]

4.1 接收输入

当系统 收到新的按钮或轴输入值 时,执行以下步骤:

  1. gamepad 为表示收到新按钮或轴输入值的设备的 Gamepad 对象。
  2. gamepad相关全局对象游戏手柄任务源排队一个全局任务,以 更新游戏手柄状态

要为 gamepad 更新游戏手柄状态,执行以下步骤:

  1. nowgamepad相关全局对象当前高分辨率时间
  2. gamepad.[[timestamp]] 设置为 now
  3. 执行 映射并归一化轴 的步骤。
  4. 执行 映射并归一化按钮 的步骤。
  5. 执行 记录触摸 的步骤。
  6. navigatorgamepad相关全局对象Navigator 对象。
  7. 如果 navigator.[[hasGamepadGesture]]falsegamepad 包含游戏手柄用户手势
    1. navigator.[[hasGamepadGesture]] 设置为 true
    2. 对于 navigator.[[gamepads]] 中的 每一项 connectedGamepad
      1. 如果 connectedGamepad 不等于 null
        1. connectedGamepad.[[exposed]] 设置为 true
        2. connectedGamepad.[[timestamp]] 设置为 now
        3. documentgamepad相关全局对象关联 Document;否则为 null
        4. 如果 document 不为 null 且为 完全活跃的,则在 游戏手柄任务源排队一个全局任务,以在 gamepad相关全局对象触发 名为 gamepadconnected 的事件,使用 GamepadEvent,并将其 gamepad 属性初始化为 connectedGamepad

要为 gamepad 映射并归一化轴,执行以下步骤:

  1. axisValues 为表示 gamepad 代表的设备每个轴输入的最新逻辑轴输入值的 列表
  2. maxRawAxisIndexaxisValues大小 - 1。
  3. 对于 0 到 maxRawAxisIndex 范围内的 每一项 rawAxisIndex
    1. mappedIndexgamepad.[[axisMapping]][rawAxisIndex]。
    2. logicalValueaxisValues[rawAxisIndex]。
    3. logicalMinimumgamepad.[[axisMinimums]][rawAxisIndex]。
    4. logicalMaximumgamepad.[[axisMaximums]][rawAxisIndex]。
    5. normalizedValue 为 2 * (logicalValuelogicalMinimum) / (logicalMaximumlogicalMinimum) − 1。
    6. gamepad.[[axes]][axisIndex] 设置为 normalizedValue

要为 gamepad 映射并归一化按钮,执行以下步骤:

  1. buttonValues 为表示 gamepad 代表的设备每个按钮输入的最新逻辑按钮输入值的 列表
  2. maxRawButtonIndexbuttonValues大小 - 1。
  3. 对于 0 到 maxRawButtonIndex 范围内的 每一项 rawButtonIndex
    1. mappedIndexgamepad.[[buttonMapping]][rawButtonIndex]。
    2. logicalValuebuttonValues[rawButtonIndex]。
    3. logicalMinimumgamepad.[[buttonMinimums]][rawButtonIndex]。
    4. logicalMaximumgamepad.[[buttonMaximums]][rawButtonIndex]。
    5. normalizedValue 为 (logicalValuelogicalMinimum) / (logicalMaximumlogicalMinimum)。
    6. buttongamepad.[[buttons]][mappedIndex]。
    7. button.[[value]] 设置为 normalizedValue
    8. 如果按钮具有指示纯按下或释放状态的数字开关,则如果按钮被按下,将 button.[[pressed]] 设置为 true,如果未按下则设置为 false

      否则,如果值高于 按钮按下阈值,则将 button.[[pressed]] 设置为 true,否则设置为 false

    9. 如果按钮具备检测触摸的能力,且按钮当前正被触摸,则将 button.[[touched]] 设置为 true

      否则,将 button.[[touched]] 设置为 button.[[pressed]]

要为 gamepad 记录触摸,执行以下步骤:

  1. 断言Gamepad.[[touches]] 为空
  2. 按触摸表面枚举顺序,对 gamepad 上的每个触摸表面重复以下步骤:
    1. surfaceId 为当前的表面枚举索引。
    2. 如果触摸表面暴露了设备单位下的最大表面维度,则将 touch.surfaceDimensions 设置为一个 DOMRectReadOnly,其 widthheight 初始化为触摸表面上设备单位下的最大 X 和 Y 维度。
    3. gamepad 针对当前触摸表面报告的每个活动触摸点重复以下步骤:
      1. touch 为一个新创建的 GamepadTouch 对象。
      2. touch.surfaceId 设置为 surfaceId
      3. 如果触摸数据是 用户代理 追踪的现有活动触摸点的一部分:
        1. touch.touchId 设置为该活动触摸点的 touchId
        2. 否则,将 touch.touchId 设置为 gamepad.[[nextTouchId]],并递增 gamepad.[[nextTouchId]]
          注意: 触摸 ID 是相对于游戏手柄的

          如果游戏手柄有多个触摸表面,触摸 ID 在各表面间将是唯一的。

      4. touch.position 设置为一个 新的 DOMPointReadOnly,其 x 初始化为相对于设备触摸表面的设备 X 坐标并归一化到 [-1 .. 1](-1 为最左侧坐标,1 为最右侧坐标),y 初始化为相对于设备触摸表面并归一化到 [-1 .. 1](-1 为最顶部坐标,1 为最底部坐标)。
        注意: 可能的实现(如果 surfaceDimensions 可用)

        x = (2.0 * touchData.x / surfaceDimensions.width) - 1
        y = (2.0 * touchData.y / surfaceDimensions.height) - 1

      5. 追加 touchgamepad.[[touches]]

4.2 构建 Gamepad

代表已连接游戏手柄设备的 Gamepad 是通过执行以下步骤构造的:

  1. gamepad 为新创建的 Gamepad 实例。
    1. gamepadid 属性初始化为游戏手柄的识别字符串。
    2. gamepadindex 属性初始化为 选择未使用游戏手柄索引 的结果。
    3. gamepadmapping 属性初始化为 选择映射 的结果。
    4. gamepad.[[connected]] 设置为 true
    5. gamepad.[[timestamp]] 设置为 gamepad相关全局对象当前高分辨率时间
    6. gamepad.[[axes]] 设置为 初始化轴 的结果。
    7. gamepad.[[buttons]] 设置为 初始化按钮 的结果。
    8. gamepad.[[vibrationActuator]] 设置为 构造 GamepadHapticActuator 的结果。
  2. 返回 gamepad

要为 gamepad 选择一个未使用的游戏手柄索引,执行以下步骤:

  1. navigatorgamepad相关全局对象Navigator 对象。
  2. maxGamepadIndexnavigator.[[gamepads]]大小 - 1。
  3. 对于 0 到 maxGamepadIndex 范围内的 每一项 gamepadIndex
    1. 如果 navigator.[[gamepads]][gamepadIndex] 为 null,则返回 gamepadIndex
  4. 追加 nullnavigator.[[gamepads]]
  5. 返回 navigator.[[gamepads]]大小 - 1。

要为 gamepad 初始化轴,执行以下步骤:

  1. inputCountgamepad 代表的设备暴露的轴输入数量。
  2. gamepad.[[axisMinimums]] 设置为一个 列表,其 大小 等于 inputCount,包含每个轴输入的逻辑最小值。
  3. gamepad.[[axisMaximums]] 设置为一个 列表,其 大小 等于 inputCount,包含每个轴输入的逻辑最大值。
  4. unmappedInputList 为一个空 列表
  5. mappedIndexList 为一个空 列表
  6. axesSize 为 0。
  7. 对于 0 到 inputCount − 1 范围内的每个 rawInputIndex
    1. 如果索引为 rawInputIndex 的游戏手柄轴 代表一个标准游戏手柄轴
      1. canonicalIndex 为该轴的 规范索引
      2. 如果 mappedIndexList 包含 canonicalIndex,则将 rawInputIndex 追加到 unmappedInputList

        否则

        1. gamepad.[[axisMapping]][rawInputIndex] 设置为 canonicalIndex
        2. 追加 canonicalIndexmappedIndexList
        3. 如果 canonicalIndex + 1 大于 axesSize,则将 axesSize 设置为 canonicalIndex + 1。

      否则,追加 rawInputIndexunmappedInputList

  8. axisIndex 为 0。
  9. 对于 unmappedInputList 中的每个 rawInputIndex
    1. mappedIndexList 包含 axisIndex
      1. 递增 axisIndex
    2. gamepad.[[axisMapping]][rawInputIndex] 设置为 axisIndex
    3. 追加 axisIndexmappedIndexList
    4. 如果 axisIndex + 1 大于 axesSize,则将 axesSize 设置为 axisIndex + 1。
  10. axes 为一个空 列表
  11. 对于 0 到 axesSize − 1 范围内的每个 axisIndex追加 0 到 axes
  12. 返回 axes

要为 gamepad 初始化按钮,请执行以下步骤

  1. inputCountgamepad 所代表设备暴露的按钮输入数量。
  2. gamepad.[[buttonMinimums]] 设置为一个 列表,包含大小等于 inputCountunsigned long 值,其中包含每个按钮输入的最小逻辑值。
  3. gamepad.[[buttonMaximums]] 设置为一个 列表,包含大小等于 inputCountunsigned long 值,其中包含每个按钮输入的最大逻辑值。
  4. unmappedInputList 为一个空 列表
  5. mappedIndexList 为一个空 列表
  6. buttonsSize 为 0。
  7. 对于 0 到 inputCount − 1 范围内的每个 rawInputIndex
    1. 如果索引为 rawInputIndex 的游戏手柄按钮 代表一个标准游戏手柄按钮
      1. canonicalIndex 为该按钮的 规范索引
      2. 如果 mappedIndexList 包含 canonicalIndex,则将 rawInputIndex 追加到 unmappedInputList

        否则

        1. gamepad.[[buttonMapping]][rawInputIndex] 设置为 canonicalIndex
        2. 追加 canonicalIndexmappedIndexList
        3. 如果 canonicalIndex + 1 大于 buttonsSize,则将 buttonsSize 设置为 canonicalIndex + 1。

      否则,追加 rawInputIndexunmappedInputList

    2. 递增 rawInputIndex
  8. buttonIndex 为 0。
  9. 对于 unmappedInputList 中的每个 rawInputIndex
    1. mappedIndexList 包含 buttonIndex
      1. 递增 buttonIndex
    2. gamepad.[[buttonMapping]][rawInputIndex] 设置为 buttonIndex
    3. 追加 buttonIndexmappedIndexList
    4. 如果 buttonIndex + 1 大于 buttonsSize,则将 buttonsSize 设置为 buttonIndex + 1。
  10. buttons 为一个空 列表
  11. 对于 0 到 buttonsSize − 1 范围内的每个 buttonIndex追加一个 新的 GamepadButtonbuttons
  12. 返回 buttons

5. GamepadButton 接口

此接口定义了游戏手柄设备上单个按钮的状态。

WebIDL[Exposed=Window]
interface GamepadButton {
  readonly attribute boolean pressed;
  readonly attribute boolean touched;
  readonly attribute double value;
};

GamepadButton 实例创建时带有下表中描述的内部槽位

内部槽位 初始值 描述(非规范性)
[[pressed]] false 指示按钮被按下的标志
[[touched]] false 指示按钮被触摸的标志
[[value]] 0.0 一个 double 值,表示缩放到 [0 .. 1] 范围内的按钮值
pressed 属性

按钮的按下状态。如果按钮当前处于按下状态,则此属性 必须true,否则为 false。对于没有数字开关来指示纯粹按下或释放状态的按钮,用户代理 必须 选择一个 按钮按下阈值,以在值高于一定数值时指示按钮被按下。如果平台 API 提供了建议值,用户代理 应该 使用该值。在其他情况下,用户代理 应该 选择其他合理的数值。

pressed 获取器步骤为

  1. 返回 .[[pressed]]
touched 属性

按钮的触摸状态。如果按钮能够检测触摸,则此属性 必须 在按钮当前被触摸时为 true,否则为 false。如果按钮不能检测触摸但能报告模拟值,则此属性 必须 在 value 属性大于 0 时为 true,value 为 0 时为 false。如果按钮不能检测触摸且只能报告数字值,则此属性 必须 反映 pressed 属性。

touched 获取器步骤为

  1. 返回 .[[touched]]
value 属性

对于带有模拟传感器的按钮,此属性 必须 表示按钮被按下的程度。所有按钮值 必须 线性归一化到 [0 .. 1] 范围。0 必须 表示完全未按下,1 必须 表示完全按下。对于没有模拟传感器的按钮,必须仅提供 0 和 1 的值,分别代表完全未按下和完全按下。

value 获取器步骤为

  1. 返回 .[[value]]

6. GamepadTouch 接口

此接口定义了支持此类输入的游戏手柄触摸表面上的触摸。该对象包含一个 touchId,它从输入介质(例如手指、触控笔等)接触触摸设备那一刻起,直到输入介质不再与触摸设备接触为止,唯一地标识该触摸点。

WebIDLdictionary GamepadTouch {
  unsigned long touchId;
  octet surfaceId;
  DOMPointReadOnly position;
  DOMRectReadOnly? surfaceDimensions;
};
touchId 属性
触摸的唯一 ID。范围为 [0 .. 4294967295]。
surfaceId 属性
生成触摸的表面的唯一 ID。
position 属性
一个 DOMPointReadOnly,它保存了触摸的 x, y 坐标。z 和 w 值当前未使用。每个坐标的范围归一化为 [-1 .. 1]。沿 x 轴,-1 指最左侧坐标,1 指最右侧坐标。沿 y 轴,-1 指最顶部坐标,1 指最底部坐标。
surfaceDimensions 属性
一个使用整数单位的触摸表面 widthheight 初始化的 DOMRectReadOnly。如果不可用,则为 null

7. GamepadMappingType 枚举

此枚举定义了游戏手柄的一组已知映射。

WebIDLenum GamepadMappingType {
  "",
  "standard",
  "xr-standard",
};
""
空字符串表示此游戏手柄未使用任何映射。
"standard"
游戏手柄的控件已映射到 标准游戏手柄 布局。
"xr-standard"
游戏手柄的控件已映射到 "xr-standard" 游戏手柄映射。此映射保留供 WebXR 游戏手柄模块 - 1 级 使用。getGamepads() 返回的 Gamepad 对象 绝不 报告 "xr-standard" 的 mapping

8. GamepadHapticActuator 接口

GamepadHapticActuator 对应于可施加力以实现触觉反馈的电机或其他执行器的配置。

WebIDL[Exposed=Window]
interface GamepadHapticActuator {
  [SameObject] readonly attribute FrozenArray<GamepadHapticEffectType> effects;
  Promise<GamepadHapticsResult> playEffect(
      GamepadHapticEffectType type,
      optional GamepadEffectParameters params = {}
  );
  Promise<GamepadHapticsResult> reset();
};

GamepadHapticActuator 实例创建时带有下表中描述的内部槽位

内部槽位 初始值 描述
[[effects]] 一个空的 GamepadHapticEffectType 列表 表示执行器支持的效果。
[[playingEffectPromise]] null 播放某个效果的 Promise,如果没有效果正在播放,则为 null
effects 属性

表示执行器支持的所有触觉效果类型的 GamepadHapticEffectType 值数组。此属性列出了执行器支持的 GamepadHapticEffectType 值,除非 用户代理 不支持播放该类型的效果。

effects 获取器步骤为

  1. 返回 .[[effects]]
playEffect() 方法

调用 playEffect() 方法的步骤,使用 GamepadHapticEffectType typeGamepadEffectParameters params,为

  1. 如果 params 没有描述 type 类型的 有效效果,则返回 一个被拒绝的 promise,并附带一个 TypeError
  2. document当前设置对象相关全局对象关联 Document
  3. 如果 documentnulldocument 不是 完全激活的,或者 document可见性状态"hidden",则返回 一个被拒绝的 promise,并附带 "InvalidStateError" DOMException
  4. 如果 .[[playingEffectPromise]] 不为 null
    1. effectPromise.[[playingEffectPromise]]
    2. .[[playingEffectPromise]] 设置为 null
    3. 游戏手柄任务源上,使用 对象的 相关全局对象排入一个全局任务,以用 "preempted" 解决 (resolve) effectPromise
  5. 如果 GamepadHapticActuator 不能 播放 type 类型的效果,则返回 一个被拒绝的 promise,理由为 NotSupportedError
  6. [[playingEffectPromise]]一个新的 promise
  7. playEffectTimestamp 为基于 document相关全局对象当前高分辨率时间
  8. 并行执行以下步骤
    1. 对执行器 发布一个触觉效果,参数为 type, paramsplayEffectTimestamp
    2. 当效果完成时,如果 .[[playingEffectPromise]] 不为 null,则在 游戏手柄任务源上,使用 对象的 相关全局对象排入一个全局任务,以运行以下步骤
      1. 如果 .[[playingEffectPromise]]null,则中止这些步骤。
      2. 解决 .[[playingEffectPromise]],使用 "complete"。
      3. .[[playingEffectPromise]] 设置为 null
  9. 返回 [[playingEffectPromise]]
reset() 方法

reset() 方法的步骤为

  1. document当前设置对象相关全局对象关联 Document
  2. 如果 documentnulldocument 不是 完全激活的,或者 document可见性状态"hidden",则返回 一个被拒绝的 promise,并附带 "InvalidStateError" DOMException
  3. resetResultPromise一个新的 promise
  4. 如果 .[[playingEffectPromise]] 不为 null,则并行执行以下步骤
    1. effectPromise.[[playingEffectPromise]]
    2. 停止触觉效果,在 游戏手柄的执行器上。
    3. 如果效果已成功停止,执行
      1. 如果 effectPromise.[[playingEffectPromise]] 仍然相同,则将 .[[playingEffectPromise]] 设置为 null
      2. 游戏手柄任务源上,使用 对象的 相关全局对象排入一个全局任务,以用 "preempted" 解决 (resolve) effectPromise
    4. 解决 (resolve) resetResultPromise,使用 "complete"
  5. 返回 resetResultPromise

如果 type 可以在 [[effects]] 列表中找到,则 GamepadHapticActuator 可以 播放 type 类型的效果

要检查 GamepadHapticEffectType typeGamepadEffectParameters params 是否描述了一个 有效效果,请执行以下步骤

  1. 根据 GamepadHapticEffectType type 的值,切换
    "dual-rumble"
    如果 params 没有描述 有效的双重震动效果,则返回 false
    "trigger-rumble"
    如果 params 没有描述 有效的触发器震动效果,则返回 false
  2. 返回 true

要对执行器 发布触觉效果用户代理 必须 向设备发送命令以呈现 type 类型的效果,并尝试使用提供的 params。当 params.startDelay 不为 0.0 时,用户代理 应该 使用提供的 playEffectTimestamp 以获得更精确的播放时序。用户代理 可以 修改效果以增加兼容性。例如,为震动电机设计的效果可以转换为支持波形触觉但缺少震动电机的设备的波形效果。

要对执行器 停止触觉效果用户代理 必须 向设备发送命令以中止当前正在播放的任何效果。如果触觉效果被中断,执行器 应该 尽可能快地恢复到静止状态。

8.1 处理可见性变更

document可见性状态 变为 "hidden" 时,对每个 GamepadHapticActuator actuator 执行这些步骤

  1. 如果 actuator.[[playingEffectPromise]]null,则中止这些步骤。
  2. 游戏手柄任务源上,使用 actuator相关全局对象排入一个全局任务,以运行以下步骤
    1. If actuator.[[playingEffectPromise]]null,则中止这些步骤。
    2. 解决 (resolve) actuator.[[playingEffectPromise]],使用 "preempted"。
    3. actuator.[[playingEffectPromise]] 设置为 null
  3. 停止触觉效果,在 actuator 上。

8.2 构建 GamepadHapticActuator

一个新的 gamepadHapticActuator 代表一个 Gamepad 的主振动执行器,通过执行以下步骤构建

  1. gamepadHapticActuator 为一个新创建的 GamepadHapticActuator 实例。
  2. supportedEffectsList 为一个空 列表
  3. 对于 GamepadHapticEffectType 的每个枚举值 type,如果 用户代理 可以发送命令在该执行器上启动该类型的效果,则将 type 追加到 supportedEffectsList
  4. gamepadHapticActuator.[[effects]] 设置为 supportedEffectsList

9. GamepadHapticsResult 枚举

WebIDLenum GamepadHapticsResult {
  "complete",
  "preempted"
};
complete

触觉效果播放完成。

preempted

当前效果被另一个效果停止或替换(即“抢占”)。

10. GamepadHapticEffectType 枚举

效果类型定义了执行器如何解释效果参数。

WebIDLenum GamepadHapticEffectType {
  "dual-rumble",
  "trigger-rumble"
};
"dual-rumble" 效果类型

"dual-rumble" 描述了一种触觉配置,在标准游戏手柄的每个手柄中都有一个偏心旋转质量 (ERM) 振动电机。在这种配置中,任一电机都能使整个游戏手柄振动。每个电机产生的振动效果是不等的,因此可以将它们结合起来以产生更复杂的触觉效果。

"dual-rumble" 效果是一种针对此类执行器的固定持续时间、恒定强度的振动效果。"dual-rumble" 效果由 startDelaydurationstrongMagnitudeweakMagnitude 定义,这些都不是必需的,因为它们默认为 0。

strongMagnitudeweakMagnitude 设置低频和高频振动的强度级别,归一化到 [0 .. 1] 范围,默认值为 0。

给定 GamepadEffectParameters params,一个 有效的双重震动效果 必须具有 有效的 duration有效的 startDelay,并且 strongMagnitudeweakMagnitude 都必须在 [0 .. 1] 范围内。

"trigger-rumble" 效果类型

"trigger-rumble" 描述了一种触觉配置,在 标准游戏手柄 的每个底部前部按钮(规范索引 6 和 7 的按钮)中都有一个振动电机,此外还有用于 "dual-rumble" 的两个手柄电机。这些按钮通常采用弹簧触发器的形式。在这种配置中,任一电机都能在按钮表面提供局部的触觉反馈。

"trigger-rumble" 效果是一种针对此类执行器的固定持续时间、恒定强度的振动效果。"trigger-rumble" 效果由 startDelaydurationstrongMagnitudeweakMagnitudeleftTriggerrightTrigger 定义,这些都不是必需的,因为它们默认为 0。

startDelay, duration, strongMagnitude, weakMagnitude 与 "dual-rumble" 具有相同的定义。leftTriggerrightTrigger 分别设置左侧和右侧底部前部按钮振动的强度级别,归一化到 [0 .. 1] 范围,默认值为 0。

给定 GamepadEffectParameters params,一个 有效的触发器震动效果 必须具有 有效的 duration有效的 startDelay,并且 strongMagnitude, weakMagnitude, leftTrigger, 和 rightTrigger 必须都在 [0 .. 1] 范围内。

11. GamepadEffectParameters 字典

GamepadEffectParameters 字典包含触觉效果所使用的参数键。每个键的含义由触觉效果定义,某些键可能未使用。

为了减少不必要的长时间运行效果,用户代理 可以有效效果 的总持续时间限制为某个最大持续时间。用户代理 建议 使用最长 5 秒的时间。

WebIDLdictionary GamepadEffectParameters {
    unsigned long long duration = 0;
    unsigned long long startDelay = 0;
    double strongMagnitude = 0.0;
    double weakMagnitude = 0.0;
    double leftTrigger = 0.0;
    double rightTrigger = 0.0;
};
duration 成员
duration 设置振动效果的持续时间(以毫秒为单位)。
startDelay 成员
startDelay 设置从调用 playEffect() 到振动开始之间的延迟持续时间(以毫秒为单位)。在延迟间隔期间,执行器 不应该 振动。
strongMagnitude 成员
"dual-rumble" 或 "trigger-rumble" 效果中低频震动的振动幅度。
weakMagnitude 成员
"dual-rumble" 或 "trigger-rumble" 效果中高频震动的振动幅度。
leftTrigger 成员
"trigger-rumble" 效果中左下前按钮(规范索引 6)震动的振动幅度。
rightTrigger 成员
"trigger-rumble" 效果中右下前按钮(规范索引 7)震动的振动幅度。

12. Navigator 接口的扩展

WebIDL[Exposed=Window]
partial interface Navigator {
  sequence<Gamepad?> getGamepads();
};

Navigator 实例创建时带有下表中描述的内部槽位

内部槽位 初始值 描述(非规范性)
[[hasGamepadGesture]] false 指示已观察到 游戏手柄用户手势 的标志
[[gamepads]] 一个空的 Gamepad? 对象 序列 每个存在于其 index 属性指定索引处的 Gamepad,未分配的索引为 null

12.1 getGamepads() 方法

getGamepads() 返回的游戏手柄状态在 gamepaddisconnectedgamepadconnected 事件触发之前,不会反映断开连接或连接。

为了减轻指纹识别,getGamepads() 在看到 游戏手柄用户手势 之前返回一个空 列表。[FINGERPRINTING-GUIDANCE]

getGamepads() 方法步骤为

  1. doc当前全局对象关联 Document
  2. 如果 docnulldoc 不是 完全激活的,则返回一个空 列表
  3. 如果 doc 不被 允许使用 "gamepad" 权限,则 抛出 "SecurityError" DOMException 并中止这些步骤。
  4. 如果 .[[hasGamepadGesture]]false,则返回一个空 列表
  5. now 为基于 当前全局对象当前高分辨率时间
  6. gamepads 为一个空 列表
  7. 对于 .[[gamepads]] 中的每个 gamepad
    1. 如果 gamepad 不为 nullgamepad.[[exposed]]false
      1. gamepad.[[exposed]] 设置为 true
      2. gamepad.[[timestamp]] 设置为 now.
    2. 追加 gamepadgamepads
  8. 返回 gamepads

如果当前的输入状态指示用户正在与游戏手柄进行交互,则 gamepad 包含游戏手柄用户手势用户代理 必须 提供一种算法来检查输入状态是否包含游戏手柄用户手势。对于支持中性默认值并且至少报告过一次 pressed 值为 false 的按钮,pressed 值为 true 应该 被视为交互。如果按钮不支持中性默认值(例如切换开关),则 pressed 值为 true 不应该 被视为交互。如果按钮从未报告过 pressed 值为 false,则它 不应该 被视为交互。如果轴支持中性默认值,当前从中性的位移大于 用户代理 选择的阈值,并且轴至少报告过一次低于该阈值的值,则轴运动 应该 被视为交互。如果轴不支持中性默认值(例如不支持自定心的操纵杆轴),或者轴从未报告过低于轴手势阈值的值,则在检查交互时 不应该 考虑该轴。轴手势阈值 应该 足够大,以免随机抖动被视为交互。

13. GamepadEvent 接口

WebIDL[Exposed=Window]

interface GamepadEvent: Event {
  constructor(DOMString type, GamepadEventInit eventInitDict);
  [SameObject] readonly attribute Gamepad gamepad;
};
gamepad 属性
gamepad 属性提供对与此事件关联的游戏手柄数据的访问。

13.1 GamepadEventInit 字典

WebIDLdictionary GamepadEventInit : EventInit {
  required Gamepad gamepad;
};
gamepad 成员
与此事件关联的 Gamepad

14. 重映射

每个设备制造商都创造了许多不同的产品,每种产品都有独特的按钮和轴样式及布局。预期 用户代理 将尽可能多地支持这些产品。

此外,还有游戏机普及的事实标准布局。当 用户代理 识别出连接的设备时,建议 在可能的情况下将其重新映射为规范顺序。未识别的设备仍应以其原始形式暴露。

目前有一个规范布局,即 标准游戏手柄。重新映射时,axesbuttons 中的索引应尽可能紧密地对应于下图中的物理位置。此外,mapping 应该 设置为 "standard"。

标准游戏手柄 按钮布局包括左侧四个按钮的簇、右侧四个按钮的簇、中心三个按钮的簇,以及游戏手柄左右两侧的一对前置按钮。"标准游戏手柄"的四个轴与一对模拟摇杆相关联,一个在左,一个在右。下表描述了按钮/轴及其物理位置。

如果轴输入报告了拇指摇杆轴的输入值,该拇指摇杆位于与对应 标准游戏手柄 拇指摇杆大致相同的位置,并且轴的方向(上下或左右)与 标准游戏手柄 轴的方向匹配,则该轴输入 代表一个标准游戏手柄轴。如果有多个轴代表同一个 标准游戏手柄 轴,则 用户代理 应该 选择一个作为 标准游戏手柄 轴,并为另一个轴分配不同的索引。

如果按钮输入报告了按钮或触发器的输入值,且该按钮或触发器位于与对应 标准游戏手柄 按钮大致相同的位置,则该按钮输入 代表一个标准游戏手柄按钮

如果轴或按钮输入代表 标准游戏手柄 轴或按钮,则其 规范索引 是对应 标准游戏手柄 轴或按钮的索引。

类型 索引 位置 (Location)
Button 0 右侧簇中的底部按钮
1 右侧簇中的右侧按钮
2 右侧簇中的左侧按钮
3 右侧簇中的顶部按钮
4 左上前按钮
5 右上前按钮
6 左下前按钮
7 右下前按钮
8 中心簇中的左侧按钮
9 中心簇中的右侧按钮
10 左摇杆按下按钮
11 右摇杆按下按钮
12 左侧簇中的顶部按钮
13 左侧簇中的底部按钮
14 左侧簇中的左侧按钮
15 左侧簇中的右侧按钮
16 中心簇中的中心按钮
axes 0 左摇杆水平轴(负向为左/正向为右)
1 左摇杆垂直轴(负向为上/正向为下)
2 右摇杆水平轴(负向为左/正向为右)
3 右摇杆垂直轴(负向为上/正向为下)
1 标准游戏手柄 布局的可视化表示。

14.1 指纹识别缓解

检查 Gamepad 对象的能力可能会被用作主动指纹识别的一种手段。用户代理(user agent可以更改通过 API 暴露的设备信息,以减少指纹识别面。例如,实现可以要求 Gamepad 对象拥有在 标准手柄 (Standard Gamepad) 布局中定义的按钮和轴的精确数量,即使所连接的设备实际存在更多或更少的输入。 [FINGERPRINTING-GUIDANCE]

15. 使用示例

本节是非规范性的。

下面的示例演示了访问手柄的典型方式。请注意与 requestAnimationFrame() 方法的关系。

function runAnimation() {
    window.requestAnimationFrame(runAnimation);
    for (const pad of navigator.getGamepads()) {
      // todo; simple demo of displaying pad.axes and pad.buttons
      console.log(pad);
    }
}

window.requestAnimationFrame(runAnimation);
最佳实践 1: requestAnimationFrame() 的协调

交互式应用程序通常会使用 requestAnimationFrame() 方法来驱动动画,并希望将动画与用户手柄输入进行协调。因此,应尽可能在执行动画回调之前立即轮询手柄数据,且频率需与动画频率匹配。也就是说,如果动画回调以 60Hz 运行,则手柄输入也应以该速率进行采样。

16. gamepadconnected 事件

当系统中有手柄可用时,执行以下步骤:

  1. document当前全局对象关联 Document;否则为 null
  2. 如果 document 不为 null不允许使用 "gamepad" 权限,则中止这些步骤。
  3. 手柄任务源 上对 当前全局对象 排入一个全局任务,以执行以下步骤:
    1. gamepad 为表示该手柄的 新的 Gamepad
    2. navigatorgamepad相关全局对象Navigator 对象。
    3. navigator.[[gamepads]][gamepad.index] 设置为 gamepad
    4. 如果 navigator.[[hasGamepadGesture]]true
      1. gamepad.[[exposed]] 设置为 true
      2. 如果 document 不为 null完全活跃,则在 gamepad相关全局对象 上,使用其 gamepad 属性初始化为 gamepadGamepadEvent触发一个名为 gamepadconnected 的事件。

实现本规范的 用户代理 必须提供一个名为 gamepadconnected 的新 DOM 事件。相应的事件 必须GamepadEvent 类型,并 必须Window 对象上触发。

当用户连接手柄时,用户代理 必须 分发此事件类型以作指示。如果页面加载时手柄已连接,则 应该 在用户按下按钮或移动轴时分发 gamepadconnected 事件。

17. gamepaddisconnected 事件

当系统中有手柄变为不可用时,执行以下步骤:

  1. gamepad 为代表不可用设备的 Gamepad
  2. 手柄任务源 上对 gamepad相关全局对象 排入一个全局任务,以执行以下步骤:
    1. gamepad.[[connected]] 设置为 false
    2. documentgamepad相关全局对象关联 Document;否则为 null
    3. 如果 gamepad.[[exposed]]true,且 document 不为 null完全活跃,则在 gamepad相关全局对象 上,使用其 gamepad 属性初始化为 gamepadGamepadEvent触发一个名为 gamepaddisconnected 的事件。
    4. navigatorgamepad相关全局对象Navigator 对象。
    5. navigator.[[gamepads]][gamepad.index] 设置为 null
    6. navigator.[[gamepads]] 不为空navigator.[[gamepads]] 的最后一个 null 时,移除 navigator.[[gamepads]] 的最后一个

实现本规范的 用户代理 必须提供一个名为 gamepaddisconnected 的新 DOM 事件。相应的事件 必须GamepadEvent 类型,并 必须Window 对象上触发。

当手柄与 用户代理 断开连接时,如果 用户代理 此前已向某个 Window 分发过该手柄的 gamepadconnected 事件,则 必须 向同一个 Window 分发一个 gamepaddisconnected 事件。

18. 其他事件

仍需讨论是否包含或排除轴和按钮变更事件,以及是否将它们合并("gamepadchanged"?)、稍微分离("gamepadaxischanged"?)或者按单个轴和按钮进行分离。

19. WindowEventHandlers 接口混合的扩展

本规范扩展了来自 HTML 的 WindowEventHandlers 接口混合,以添加 事件处理程序 IDL 属性,从而促进事件处理程序的注册。

WebIDLpartial interface mixin WindowEventHandlers {
  attribute EventHandler ongamepadconnected;
  attribute EventHandler ongamepaddisconnected;
};

20. 与权限策略的集成

本规范定义了一个由策略控制的功能,标识为字符串 "gamepad"。其 默认允许列表*

文档权限策略 决定了该文档中的任何内容是否被允许访问 getGamepads()。如果在任何文档中被禁用,则该文档中的内容将 不允许使用 getGamepads(),也不会触发 gamepadconnectedgamepaddisconnected 事件。

21. 一致性

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

本文档中的关键词 MAY(可以)、MUST(必须)、MUST NOT(不得)、RECOMMENDED(推荐)、SHOULD(应该)和 SHOULD NOT(不应该)仅在它们以全大写形式出现时,按 BCP 14 [RFC2119] [RFC8174] 中的描述进行解释,如此处所示。

A. 致谢

本节是非规范性的。

以下人员为本文件的制定做出了贡献。

B. 参考资料

B.1 规范性参考资料

[dom]
DOM Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://dom.spec.whatwg.org/
[FINGERPRINTING-GUIDANCE]
缓解 Web 规范中的浏览器指纹识别. Nick Doty; Tom Ritter. W3C. 2025年3月21日. W3C 工作组说明. URL: https://w3org.cn/TR/fingerprinting-guidance/
[geometry-1]
几何接口模块第 1 级. Simon Pieters; Chris Harrelson. W3C. 2018年12月4日. W3C 候选推荐标准. URL: https://w3org.cn/TR/geometry-1/
[HR-TIME]
高分辨率时间 (High Resolution Time). Yoav Weiss. W3C. 2024 年 11 月 7 日. W3C 工作草案. URL: https://w3org.cn/TR/hr-time-3/
[html]
HTML 标准. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. 活标准. URL: https://html.whatwg.cn/multipage/
[infra]
Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
[permissions-policy]
权限策略. Ian Clelland. W3C. 2025年5月6日. W3C 工作草案. URL: https://w3org.cn/TR/permissions-policy-1/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. 1997年3月. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
RFC 2119 关键词中大小写的歧义. B. Leiba. IETF. 2017年5月. 最佳当前实践. URL: https://www.rfc-editor.org/rfc/rfc8174
[WEBIDL]
Web IDL Standard. Edgar Chen; Timothy Gu. WHATWG. Living Standard. URL: https://webidl.spec.whatwg.org/
[webxr-gamepads-module-1]
WebXR 手柄模块 - 第 1 级. Brandon Jones; Manish Goregaokar; Rik Cabanier. W3C. 2024年4月9日. W3C 工作草案. URL: https://w3org.cn/TR/webxr-gamepads-module-1/