游戏手柄

W3C 工作草案

关于此文档的更多详情
此版本:
https://www.w3.org/TR/2025/WD-gamepad-20250710/
最新发布版本:
https://www.w3.org/TR/gamepad/
最新编辑草案:
https://w3c.github.io/gamepad/
历史:
https://www.w3.org/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月03日 W3C 流程文档管辖。

1. 引言

本节是非规范性的。

一些 用户代理 连接了 游戏手柄设备。这些设备 很适合用作游戏应用的输入,并适合用于“10 英尺”用户界面(演示、媒体查看器)。

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

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

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

2. 范围

与为控制游戏而设计的外部设备交互, 如果以完全通用的方式处理, 可能会变得庞大且难以驾驭。在本规范中,我们明确选择缩小范围, 以提供一个有用的功能子集,该子集可以被广泛 实现并具有广泛用途。

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

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

我们明确排除了对更复杂设备的支持,这些设备也可能 用于某些游戏场景,包括那些进行运动 感测、深度感测、视频分析、手势识别等的设备。

3. 模型

游戏手柄 是输入控件和输出 控件的集合。输入控件 具有一组可能随时间更新的 输入 值。输入控件包括 游戏手柄 的 按钮、扳机、操纵杆、摇杆和触摸表面。输出控件 是一种会改变 游戏手柄 行为的特性,用于向与 游戏手柄 交互的用户提供反馈。输出控件包括 游戏手柄 的触觉执行器。若 用户代理 能够读取 游戏手柄输入控件 的当前状态,则该游戏手柄是 可用 的。一个 不 可用游戏手柄不可用 的。当 游戏手柄可用 的时候, 该 游戏手柄输入控件输出控件 不能 改变。

用户代理 负责:

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

3.1 输入控件布局

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

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

无障碍游戏手柄型号是指制造商意图将其作为具有 标准布局的游戏手柄的可替换替代品的游戏手柄。 例如,Xbox Adaptive Controller 和 PlayStation Access Controller 是无障碍游戏手柄型号。Xbox Wireless Controller 和 DualSense 是对应的标准 型号。

3.2 输入值

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

读取某个 输入值 会返回其 逻辑值, 即 当前状态的未缩放数值表示。输入值 还 具有一个 逻辑 最小值 和一个 逻辑 最大值,它们定义边界内 逻辑值 的最小值和最大值。

输入值 可以具有一个关联的 HID usage identifier,这是一个 32 位值,用于识别 该输入值所表示的数据类型。HID usage 并不精确 描述 输入控件布局,但按照惯例,许多 布局类似的 游戏手柄 使用类似的 usages。用户代理 SHOULD 在决定 输入控件布局 时, 依赖围绕 HID usage identifiers 的惯例。

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 task source 上排队工作。

Gamepad 的实例使用下表中描述的内部槽创建:

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

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

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

index 属性
游戏手柄在 Navigator 中的索引。当多个游戏手柄 连接到 用户 代理 时,索引 MUST 按 先到先服务的原则分配,从零开始。如果某个游戏手柄 断开连接,先前分配的索引 MUST NOT 重新分配给 仍保持连接的游戏手柄。但是,如果某个游戏手柄 断开连接,随后同一个或不同的游戏手柄 再次连接,则先前使用过的最低索引 MUST 被重用。
connected 属性

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

connected getter 步骤为:

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

timestamp 允许作者确定 此游戏手柄的 axesbuttons 属性上次更新的 时间。每当系统从设备 接收到新的按钮或轴输入 值 时,该值 MUST 被设置为 当前高分辨率 时间。如果尚未从硬件接收到数据,则 timestamp MUSTGamepad 首次可供脚本使用时的 当前高分辨率 时间

警告

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

timestamp getter 步骤为:

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

此设备正在使用的映射。如果 用户代理 知道 设备的布局,则它 SHOULD 通过将 mapping 设置为对应的 GamepadMappingType 值,来指示正在使用某个映射。

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

  1. 如果游戏手柄设备的按钮和轴布局对应于 Standard Gamepad 布局,则返回 "standard"。
  2. 返回 ""。
axes 属性

游戏手柄所有轴的值数组。所有轴值 MUST 线性归一化到范围 [-1 .. 1]。如果控制器 垂直于地面且方向摇杆指向上方, -1 SHOULD 对应于“forward”或“left”,而 1 SHOULD 对应于“backward”或“right”。来自 2D 输入设备的轴 SHOULD 在 axes 数组中彼此相邻出现,先 X 后 Y。RECOMMENDED 轴按重要性递减的顺序出现, 使元素 0 和 1 通常表示方向摇杆的 X 和 Y 轴。同一对象 MUST 被返回, 直到 用户代理 需要返回不同的值(或 不同顺序的值)。

axes getter 步骤为:

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

游戏手柄所有按钮的按钮状态数组。RECOMMENDED 按钮按重要性递减的顺序出现,使 主按钮、次按钮、第三按钮等 作为元素 0、1、2、... 出现在 buttons 数组中。同一 对象 MUST 被返回,直到 用户代理 需要返回 不同的值(或不同顺序的值)。

buttons getter 步骤为:

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

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

touches getter 步骤为:

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

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

vibrationActuator getter 步骤 为:

  1. 返回 this.[[vibrationActuator]]

4.1 接收输入

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

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

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

  1. now 为给定 gamepad相关 全局对象当前高分辨率 时间
  2. gamepad.[[timestamp]] 设置为 now
  3. gamepad 运行 映射并归一化轴 的步骤。
  4. gamepad 运行 映射并归一化按钮 的步骤。
  5. gamepad 运行 记录触摸 的步骤。
  6. navigatorgamepad相关 全局对象Navigator 对象。
  7. 如果 navigator.[[hasGamepadGesture]]false,并且 gamepad 包含游戏手柄用户手势
    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 task source排队 一个全局任务,以在 gamepad相关 全局对象 上,使用 GamepadEvent 触发一个事件, 名为 gamepadconnected, 并将其 gamepad 属性初始化为 connectedGamepad

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

  1. axisValues 为一个由 unsigned long 值组成的 列表, 表示由 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 为一个由 unsigned long 值组成的 列表, 表示由 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 相对于 Gamepad

          如果 Gamepad 有多个触摸表面,则触摸 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. touch 追加gamepad.[[touches]]

4.2 构造 Gamepad

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

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

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

  1. navigatorgamepad相关 全局对象Navigator 对象。
  2. maxGamepadIndexnavigator.[[gamepads]]大小 − 1。
  3. 对从 0 到 maxGamepadIndex范围 中的每个 gamepadIndex 执行以下操作
    1. 如果 navigator.[[gamepads]][gamepadIndex] 为 null,则返回 gamepadIndex
  4. null 追加navigator.[[gamepads]]
  5. 返回 navigator.[[gamepads]]大小 − 1。

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

  1. inputCount 为由 gamepad 所代表设备暴露的轴输入数量。
  2. gamepad.[[axisMinimums]] 设置为一个由 unsigned long 值组成的 列表,其 大小 等于 inputCount, 包含每个轴输入的最小逻辑值。
  3. gamepad.[[axisMaximums]] 设置为一个由 unsigned long 值组成的 列表,其 大小 等于 inputCount, 包含每个轴输入的最大逻辑值。
  4. unmappedInputList 为一个空的 列表
  5. mappedIndexList 为一个空的 列表
  6. axesSize 为 0。
  7. 对从 0 到 inputCount − 1 的 范围 中的每个 rawInputIndex 执行以下操作
    1. 如果索引 rawInputIndex 处的游戏手柄轴 表示 Standard Gamepad 轴
      1. canonicalIndex 为该轴的 规范索引
      2. 如果 mappedIndexList 包含 canonicalIndex, 则将 rawInputIndex 追加到 unmappedInputList

        否则:

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

      否则,将 rawInputIndex 追加unmappedInputList

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

要为 gamepad 初始化按钮,运行以下步骤:

  1. inputCount 为由 gamepad 所代表设备暴露的按钮输入数量。
  2. gamepad.[[buttonMinimums]] 设置为一个由 unsigned long 值组成的 列表, 其 大小 等于 inputCount, 包含每个按钮输入的最小逻辑值。
  3. gamepad.[[buttonMaximums]] 设置为一个由 unsigned long 值组成的 列表, 其 大小 等于 inputCount, 包含每个按钮输入的最大逻辑值。
  4. unmappedInputList 为一个空的 列表
  5. mappedIndexList 为一个空的 列表
  6. buttonsSize 为 0。
  7. 对从 0 到 inputCount − 1 的 范围 中的每个 rawInputIndex 执行以下操作
    1. 如果索引 rawInputIndex 处的游戏手柄按钮 表示 Standard Gamepad 按钮
      1. canonicalIndex 为 该按钮的 规范索引
      2. 如果 mappedIndexList 包含 canonicalIndex, 则将 rawInputIndex 追加到 unmappedInputList

        否则:

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

      否则,将 rawInputIndex 追加unmappedInputList

    2. 递增 rawInputIndex
  8. buttonIndex 为 0。
  9. unmappedInputList 中的每个 rawInputIndex 执行以下操作
    1. mappedIndexList 包含 buttonIndex 时:
      1. 递增 buttonIndex
    2. gamepad.[[buttonMapping]][rawInputIndex] 设置为 buttonIndex
    3. buttonIndex 追加mappedIndexList
    4. 如果 buttonIndex + 1 大于 buttonsSize,则将 buttonsSize 设置为 buttonIndex + 1。
  10. buttons 为一个空的 列表
  11. 对从 0 到 buttonsSize − 1 的 范围 中的每个 buttonIndex 执行以下操作,将一个 新的 GamepadButton 追加buttons
  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 属性

按钮的按下状态。如果按钮当前被按下,则此属性 MUSTtrue; 如果未被按下,则为 false。 对于没有数字开关来指示纯 按下或释放状态的按钮,用户代理 MUST 选择一个 按钮 按下阈值,以便在按钮值高于某个量时 指示按钮为已按下。如果平台 API 给出 推荐值,则 用户代理 SHOULD 使用该值。在其他 情况下,用户代理 SHOULD 选择其他某个合理的 值。

pressed getter 步骤为:

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

按钮的触摸状态。如果按钮能够 检测触摸,则当按钮当前正被触摸时,此属性 MUSTtrue, 否则为 false。如果按钮 不能检测触摸但能够报告 模拟值,则当 value 属性 大于 0 时,此属性 MUSTtrue,如果值为 0 则为 false。如果按钮不能 检测触摸且只能报告数字值, 则此属性 MUST 镜像 pressed 属性。

touched getter 步骤为:

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

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

value getter 步骤为:

  1. 返回 this.[[value]]

6. GamepadTouch 接口

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

WebIDLdictionary GamepadTouch {
  unsigned long touchId;
  octet surfaceId;
  DOMPointReadOnly position;
  DOMRectReadOnly? surfaceDimensions;
};
touchId 属性
触摸的唯一 id。范围为 [0 .. 4294967295]。
surfaceId 属性
生成该触摸的表面的唯一 id。
position 属性
一个 DOMPointReadOnly,保存 该触摸的 xy 坐标。 z 和 w 值 目前未使用。每个坐标的范围都归一化到 [-1 .. 1]。沿 x 轴,-1 表示最左坐标, 1 表示最右坐标。沿 y 轴,-1 表示最上坐标,1 表示最下 坐标。
surfaceDimensions 属性
一个 DOMRectReadOnly, 以触摸表面的 widthheight 初始化,单位为整数单位。 如果不可用,则为 null

7. GamepadMappingType 枚举

此枚举定义 Gamepad 的已知映射集合。

WebIDLenum GamepadMappingType {
  "",
  "standard",
  "xr-standard",
};
""
空字符串表示此 gamepad 未使用任何映射。
"standard"
Gamepad 的控件已映射到 Standard Gamepad 布局。
"xr-standard"
Gamepad 的控件已映射到 "xr-standard" gamepad 映射。此映射保留供 WebXR Gamepads Module - Level 1 使用。由 getGamepads() 返回的 Gamepad 对象 MUST NOT 报告 "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 getter 步骤为:

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

playEffect() 方法步骤,在使用 GamepadHapticEffectType typeGamepadEffectParameters params 调用时,为:

  1. 如果 params 未描述类型为 type有效效果,则返回一个以 TypeError 拒绝的 promise
  2. document当前 设置对象相关 全局对象关联 Document
  3. 如果 documentnull,或 document 不是 完全 活跃,或 document可见性 状态"hidden",则返回一个以 "InvalidStateError" DOMException 拒绝的 promise
  4. 如果 this.[[playingEffectPromise]] 不为 null
    1. effectPromisethis.[[playingEffectPromise]]
    2. this.[[playingEffectPromise]] 设置为 null
    3. gamepad task source 上, 使用 this相关 全局对象 排队 一个全局任务,以使用 "preempted" 兑现 effectPromise
  5. 如果 this GamepadHapticActuator 不能 播放类型为 type 的效果,则返回一个以 NotSupportedError 为理由 拒绝的 promise
  6. [[playingEffectPromise]]一个新的 promise
  7. playEffectTimestamp 为给定 document相关 全局对象当前高 分辨率时间
  8. 并行执行以下步骤:
    1. 向执行器 发出触觉 效果,使用 typeparamsplayEffectTimestamp
    2. 当效果完成时,如果 this.[[playingEffectPromise]] 不为 null,则在 gamepad task source 上,使用 this相关 全局对象 排队 一个全局任务 以运行 以下步骤:
      1. 如果 this.[[playingEffectPromise]]null,则中止这些步骤。
      2. 使用 "complete" 兑现 this.[[playingEffectPromise]]
      3. this.[[playingEffectPromise]] 设置为 null
  9. 返回 [[playingEffectPromise]]
reset() 方法

reset() 方法 步骤为:

  1. document当前 设置对象相关 全局对象关联 Document
  2. 如果 documentnull,或 document 不是 完全 活跃,或 document可见性 状态"hidden",则返回一个以 "InvalidStateError" DOMException 拒绝的 promise
  3. resetResultPromise一个新的 promise
  4. 如果 this.[[playingEffectPromise]] 不为 null,则并行执行以下步骤:
    1. effectPromisethis.[[playingEffectPromise]]
    2. this 的 gamepad 的执行器上停止触觉效果
    3. 如果效果已被成功停止,则执行:
      1. 如果 effectPromisethis.[[playingEffectPromise]] 仍然相同,则将 this.[[playingEffectPromise]] 设置为 null
      2. gamepad task source 上 使用 this相关 全局对象 排队 一个全局任务,以使用 "preempted" 兑现 effectPromise
    4. 使用 "complete" 兑现 resetResultPromise
  5. 返回 resetResultPromise

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

要检查具有 GamepadHapticEffectType typeGamepadEffectParameters params 的效果是否描述了一个 有效效果, 运行以下步骤:

  1. 给定 GamepadHapticEffectType type 的值,进行 switch:
    "dual-rumble"
    如果 params 未描述一个 有效的 dual-rumble 效果, 则返回 false
    "trigger-rumble"
    如果 params 未描述一个 有效的 trigger-rumble 效果, 则返回 false
  2. 返回 true

要在执行器上 发出 触觉效果用户代理 MUST 向设备发送命令,以渲染类型为 type 的效果,并尝试让其使用提供的 params。当 params.startDelay 不为 0.0 时,用户代理 SHOULD 使用 提供的 playEffectTimestamp 以获得更精确的 播放时序。用户 代理 MAY 修改效果以提高 兼容性。例如,原本用于 rumble 电机的效果 可以被转换为基于波形的效果,用于支持 波形触觉但缺少 rumble 电机的设备。

要在执行器上 停止触觉 效果用户代理 MUST 向设备发送命令,以中止当前正在 播放的任何效果。如果触觉效果被中断,则执行器 SHOULD 尽快返回到静止状态。

8.1 处理可见性变化

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

  1. 如果 actuator.[[playingEffectPromise]]null,则中止这些步骤。
  2. gamepad task source 上,使用 actuator相关 全局对象 排队一个全局 任务,以运行以下 步骤:
    1. 如果 actuator.[[playingEffectPromise]]null,则中止这些步骤。
    2. 使用 "preempted" 兑现 actuator.[[playingEffectPromise]]
    3. actuator.[[playingEffectPromise]] 设置为 null
  3. actuator停止触觉效果

8.2 构造 GamepadHapticActuator

表示 Gamepad 的主振动执行器的 gamepadHapticActuator 通过 执行以下步骤构造:

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

9. GamepadHapticsResult 枚举

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

触觉效果已完成播放。

preempted

当前效果已被另一个效果停止或替换(即“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, 一个 有效的 dual-rumble 效果 必须具有一个 有效的 duration、一个 有效的 startDelay,并且 strongMagnitudeweakMagnitude 都必须位于 范围 [0 .. 1] 内。

"trigger-rumble" 效果类型

"trigger-rumble" 描述了一种触觉 配置,在 Standard Gamepad 的每个底部前侧 按钮(具有 规范索引 6 和 7 的按钮)中都有一个振动电机,并且还包含用于 "dual-rumble" 的两个 握柄电机。这些按钮最 常见的形式是弹簧加载的扳机。在此 配置中,任一电机都能够在按钮表面提供局部 触觉反馈。

"trigger-rumble" 效果是 一种固定时长、恒定强度的振动效果,适用于此类型的 执行器。"trigger-rumble" 效果由 startDelaydurationstrongMagnitudeweakMagnitudeleftTriggerrightTrigger 定义, 这些都不是 必需的,因为它们默认值为 0。

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

给定 GamepadEffectParameters params, 一个 有效的 trigger-rumble 效果 必须具有一个 有效的 duration、一个 有效的 startDelay,并且 strongMagnitudeweakMagnitudeleftTriggerrightTrigger 必须位于 范围 [0 .. 1] 内。

11. GamepadEffectParameters 字典

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

为缓解不需要的长时间运行效果,用户代理 MAY有效效果 的总效果持续时间限制为某个最大 时长。RECOMMENDED 用户代理 使用最大 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() 之后到 振动开始之前的延迟 持续时间,单位为毫秒。在延迟间隔期间, 执行器 SHOULD NOT 振动。
strongMagnitude 成员
"dual-rumble" 或 "trigger-rumble" 效果中 低频 rumble 的振动强度。
weakMagnitude 成员
"dual-rumble" 或 "trigger-rumble" 效果中 高频 rumble 的振动强度。
leftTrigger 成员
"trigger-rumble" 效果中底部左前按钮(规范 索引 6)的 rumble 振动强度。
rightTrigger 成员
"trigger-rumble" 效果中 底部右前按钮 (规范索引 7)的 rumble 振动强度。

12. Navigator 接口的扩展

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

Navigator 的实例 使用下表中描述的内部槽 创建:

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

12.1 getGamepads() 方法

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

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

getGamepads() 方法步骤 为:

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

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

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. 重映射

每个设备制造商都会创建许多不同的产品,并且每种产品都有 独特的按钮和轴样式与布局。本意是 用户代理 尽可能支持尽可能多的 这些设备。

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

目前有一个规范布局,即 Standard Gamepad。重映射时,axesbuttons 中的索引应尽可能对应于 下图中的 物理位置。此外, mapping SHOULD 被设置为 "standard"。

Standard Gamepad 按钮布局为:左侧一组四个 按钮、右侧一组四个按钮、中间一组三个 按钮,以及游戏手柄左侧和右侧的一对朝前按钮。 "Standard Gamepad" 的四个轴与 一对模拟摇杆相关联,一个在左侧,一个在右侧。下 表描述了按钮/轴及其物理 位置。

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

如果某个按钮输入 报告的是按钮或扳机的输入值,并且该按钮或 扳机位于与对应的 Standard Gamepad 按钮大致相同的位置,则该按钮输入 表示一个 Standard Gamepad 按钮

如果某个轴或按钮输入表示一个 Standard Gamepad 轴或 按钮,则其 规范索引 是对应的 Standard Gamepad 轴或按钮的索引。

类型 索引 位置
按钮 0 右侧按钮组中的底部按钮
1 右侧按钮组中的右侧按钮
2 右侧按钮组中的左侧按钮
3 右侧按钮组中的顶部按钮
4 顶部左前按钮
5 顶部右前按钮
6 底部左前按钮
7 底部右前按钮
8 中间按钮组中的左侧按钮
9 中间按钮组中的右侧按钮
10 左摇杆按下按钮
11 右摇杆按下按钮
12 左侧按钮组中的顶部按钮
13 左侧按钮组中的底部按钮
14 左侧按钮组中的左侧按钮
15 左侧按钮组中的右侧按钮
16 中间按钮组中的中央按钮
0 左摇杆的水平轴(负值向左/正值向右)
1 左摇杆的垂直轴(负值向上/正值向下)
2 右摇杆的水平轴(负值向左/正值向右)
3 右摇杆的垂直轴(负值向上/正值向下)
1 Standard Gamepad 布局的可视化表示。

14.1 指纹识别缓解

检查 Gamepad 对象的能力可被用作 主动指纹识别的一种手段。用户代理 MAY 更改 通过 API 暴露的 设备信息,以减少 指纹识别表面。例如,某个实现可以要求 Gamepad 对象恰好具有 Standard Gamepad 布局中定义的按钮和轴数量, 即使已连接设备上存在更多或更少的 输入。 [FINGERPRINTING-GUIDANCE]

15. 使用示例

本节是非规范性的。

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

function runAnimation() {
    window.requestAnimationFrame(runAnimation);
    for (const pad of navigator.getGamepads()) {
      // todo;显示 pad.axes 和 pad.buttons 的简单演示
      console.log(pad);
    }
}

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

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

16. gamepadconnected 事件

当游戏手柄在系统上变为可用时,运行以下 步骤:

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

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

用户代理 MUST 分发此事件类型,以指示用户已 连接游戏手柄。如果页面加载时游戏手柄 已经连接,则当 用户按下按钮或移动轴时,gamepadconnected 事件 SHOULD 被分发。

17. gamepaddisconnected 事件

当游戏手柄在系统上变为不可用时,运行以下 步骤:

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

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

当游戏手柄从 用户代理 断开连接时,如果 用户代理 先前已向某个 Window 为该 游戏手柄分发过 gamepadconnected 事件,则 gamepaddisconnected 事件 MUST 被 分发到同一个 Window

18. 其他事件

需要更多讨论,以决定是否包含或排除轴和 按钮变化事件,以及是否将它们更合并在一起 ("gamepadchanged"?)、略微分开("gamepadaxischanged"?),或 按单个轴和按钮分别处理。

19. WindowEventHandlers 接口 Mixin 的扩展

本规范扩展 HTML 中的 WindowEventHandlers 接口 mixin, 以添加 事件处理器 IDL 属性,从而便于 事件处理器注册。

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

20. 与 Permissions Policy 的集成

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

一个 documentpermissions policy 决定该文档中的 任何内容是否被允许访问 getGamepads()。如果 在任何文档中被禁用,则该文档中的任何内容 都不 允许 使用 getGamepads(),并且 gamepadconnectedgamepaddisconnected 事件也不会触发。

21. 一致性

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

本文档中的关键词 MAYMUSTMUST NOTRECOMMENDEDSHOULDSHOULD 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://www.w3.org/TR/fingerprinting-guidance/
[geometry-1]
Geometry Interfaces Module Level 1. Simon Pieters; Chris Harrelson. W3C. 2018年12月4日. W3C 候选推荐标准. URL: https://www.w3.org/TR/geometry-1/
[HR-TIME]
High Resolution Time. Yoav Weiss. W3C. 2024年11月7日. W3C 工作草案. URL: https://www.w3.org/TR/hr-time-3/
[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/
[permissions-policy]
Permissions Policy. Ian Clelland. W3C. 2025年5月6日. W3C 工作草案. URL: https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
用于 RFC 中表示 需求级别的关键词. S. Bradner. IETF. 1997年3月. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
RFC 2119 关键词中大小写的歧义. B. Leiba. IETF. 2017年5月. Best Current Practice. 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 Gamepads Module - Level 1. Brandon Jones; Manish Goregaokar; Rik Cabanier. W3C. 2024年4月9日. W3C 工作 草案. URL: https://www.w3.org/TR/webxr-gamepads-module-1/