Copyright © 2024 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本节描述了本文档在发布时的状态。当前的 W3C 出版物列表和本技术报告的最新修订版可在 W3C 技术报告索引 中找到:https://www.w3.org/TR/。
本文档的更改历史可在 https://github.com/w3c/pointerlock/commits/gh-pages/index.html 查看。
自 W3C 2016年10月27日推荐 以来的更改摘要:
本文档由 Web 应用程序工作组 作为工作草案发布, 使用了推荐路线。
作为工作草案发布并不意味着 W3C 及其成员的认可。
这是一个草案文件,可能会随时更新、替换或被其他文件淘汰。在此文件不应被引用为其他用途,而应被视为进行中的工作。
本文档由一个根据 W3C 专利政策 运营的工作组生产。W3C 维护一个公开的专利披露列表,列出了与该组交付物有关的任何专利披露信息;该页面还包括披露专利的说明。任何个人如果知道某项专利并认为其包含必要权利要求,必须根据 W3C 专利政策第6节披露相关信息。
本文档受 2023年11月3日 W3C 进程文档 管辖。
本节为非规范性内容。
Pointer Lock API 允许应用程序直接解释鼠标移动作为输入方式,而不仅仅是读取鼠标光标的位置。一个流行的例子是 第一人称视角的三维图形应用程序(如游戏)中的运动控制:鼠标移动用于控制玩家摄像头的旋转/方向;不显示鼠标光标, 并且移动不受传统边界(如用户代理窗口或整个屏幕)的限制,这意味着鼠标移动可以在任何方向上无限跟踪。
Pointer Lock 与 Mouse Capture 相关 [MDN-SETCAPTURE](Mouse Capture 未指定:bug 14600)。 Capture 提供了在拖动鼠标时持续向目标元素传递事件的能力,但在鼠标按钮释放时停止。 Pointer Lock 的区别在于它是持久的,不受屏幕边界限制,无论鼠标按钮状态如何都发送事件,隐藏光标, 并且直到通过 API 调用或用户执行特定的 默认解锁手势 才会释放。
Pointer Lock 处理捕获单个资源并将其与单个元素关联。这类似于 Fullscreen API [FULLSCREEN], 它将单个元素提升为全屏。Pointer Lock API 选择尽可能紧密地仿照 Fullscreen API 的资源捕获、状态更改和释放 API。
Pointer Lock 交互模式以前称为鼠标锁定。名称更改的原因是,除了鼠标之外,许多不同的控制器类型都可以操纵屏幕上的指针光标, 并且它们都会受到影响。
指针锁定状态 是一个状态, 在该状态下,单个 DOM 元素(我们称之为 指针锁定目标)接收所有鼠标事件,并且光标被隐藏。
一旦进入 指针锁定状态,
用户代理 将有一个 指针锁定目标、
一个 指针锁定选项,
它是 PointerLockOptions
,
以及 光标位置,这是代表进入指针锁定状态时系统鼠标光标位置的一对数字
(与 screenX
、screenY
中报告的位置相同)。指针锁定目标
将接收所有相关的用户生成的MouseEvent
事件,
即所有用户生成的mousemove
、mousedown
、mouseup
、click
、dblclick
、auxclick
和 wheel
[ui-events]。
在 指针锁定状态 下,其他元素不会接收这些事件。
不会分派需要鼠标光标概念的事件:即
mouseenter
、mouseleave
、mouseover
、mouseout
、drag
和 drop
。
是否可以以规范的方式列出这些事件,而不仅仅是举一些例子?
我认为当前文本是可以的,因为这里缺乏精确性只是鼠标事件规范中更大问题的症状。该问题在某些方面正在得到改善, 例如在 w3c/uievents#200 中有一些进展, 如果它被实现,那么最终这段内容可以作为 UI 事件规范的一部分。
但是,在此期间,如果您能准确列出这些事件,将有助于实现互操作性。
例如,我不确定指针事件是否会受到此更改的影响。
当处于 指针锁定状态 时,
如果 指针锁定选项中的
unadjustedMovement
成员为true
,
则事件坐标不会受到底层平台行为(如鼠标加速)的影响。换句话说,用户代理使用底层平台提供的 API 来确保获取原始事件。
如果PointerLockOptions
的
unadjustedMovement
成员为false
,
则用户代理依赖底层平台的默认鼠标加速行为。
在 指针锁定状态 中, 系统鼠标光标被隐藏,并且窗口将保持焦点,不会因鼠标移动或按键操作而失去焦点。这是通过底层操作系统 API 直接或间接实现的。
由应用程序脚本创建的合成鼠标事件,无论锁定状态如何,行为都是一样的。
WebIDLdictionary PointerLockOptions
{
boolean unadjustedMovement
= false;
};
PointerLockOptions
字典
此选项字典用于自定义指针在锁定模式下的行为。
unadjustedMovement
成员
如果此值设置为true
,则指针移动将不受底层平台的修改(如鼠标加速)的影响。
两种事件用于传达指针锁定状态的变化或更改状态时的错误。它们分别命名为 pointerlockchange 和 pointerlockerror。
有关详细信息,请参阅 3. 对Element
接口的扩展 中的算法。
放大软件会增加屏幕上内容的大小。它使用鼠标移动放大的焦点。当启动指针锁定时,放大软件需要切换为使用键盘移动放大焦点。
当触发pointerlockchange
事件时,
浏览器需要确保事件传达给辅助技术,如屏幕放大器。
退出指针锁定的过程如下:
Element
接口被扩展,以提供
请求指针锁定的能力。
WebIDLpartial interface Element {
Promise<undefined> requestPointerLock
(optional PointerLockOptions
options = {});
};
const lock_element = document.getElementById("lock_element");
const lock_button = document.getElementById("lock");
lock_button.addEventListener("click", async (event) => {
try {
await lock_element.requestPointerLock({ unadjustedMovement: true });
console.log("successfully locked!");
} catch (e) {
console.log("lock failed with error: ", e);
}
});
PointerLockOptions
options) 方法
一个名为 并行队列 的 锁请求队列
用于排队所有请求。当 requestPointerLock()
被调用时,执行以下步骤:
window
处于 焦点 时,如果
this 的
shadow-including root
是 活动文档,并且属于某个
浏览上下文
(或具有不在焦点中的
祖先
浏览上下文):
pointerlockerror
,
目标是 this 的
节点文档。
WrongDocumentError
"
DOMException
。
Document
尚未释放成功的指针锁定,使用
exitPointerLock
()
:
pointerlockerror
,
目标是 this 的
节点文档。
NotAllowedError
"
DOMException
。
pointerlockerror
,
目标是 this 的
节点文档。
SecurityError
"
DOMException
。
unadjustedMovement
"]
为 true,且平台不支持
unadjustedMovement
:
pointerlockerror
,
目标是 this 的
节点文档。
NotSupportedError
"
DOMException
。
pointerlockerror
,
目标是 this 的
节点文档。
InvalidStateError
"
DOMException
。
pointerlockerror
,
目标是 this 的
节点文档。
NotSupportedError
"
DOMException
。
WebIDLpartial interface Document {
attribute EventHandler onpointerlockchange
;
attribute EventHandler onpointerlockerror
;
undefined exitPointerLock
();
};
onpointerlockchange
属性
一个用于 事件处理器
idl 属性
的 pointerlockchange
事件。
onpointerlockerror
属性
一个用于 事件处理器
idl 属性
的 pointerlockerror
事件。
exitPointerLock()
方法
WebIDLpartial interface mixin DocumentOrShadowRoot {
readonly attribute Element? pointerLockElement
;
};
pointerLockElement
当指针被锁定时,返回通过重新定向到元素的结果, 该元素是鼠标事件的目标,如果该结果和this元素在同一棵树中, 否则返回 null。
如果锁定正在进行或指针未锁定,则返回 null。
<body>
<div id="host1">
<shadow-root id="root1">
<canvas id="canvas1"></canvas>
</shadow-root>
</div>
<div id="host2">
<shadow-root id="root2">
<canvas id="canvas2"></canvas>
</shadow-root>
</div>
</body>
此示例使用虚构的 shadow-root
元素表示一个
shadow root 实例。
如果 #canvas1
是目标,document.pointerLockElement
返回
#host1
,而 root1.pointerLockElement
返回 #canvas1
。
将 #canvas1
重新定向到 #root2
的结果是 #host1
,
但由于 #host1
不在与 #root2
相同的树中,
因此对于 root2.pointerLockElement
将返回 null。
WebIDLpartial interface MouseEvent {
readonly attribute double movementX
;
readonly attribute double movementY
;
};
movementX
属性
movementY
属性
movementX
和
movementY
属性必须提供指针位置的变化,类似于在两个连续的 mousemove
事件
eNow
和 ePrevious
之间存储 screenX
和
screenY
的值并计算差异:movementX = eNow.screenX - ePrevious.screenX
。
对于除 mousemove
以外的所有鼠标事件,movementX
和
movementY
必须为零。所有运动数据必须通过 mousemove
事件传递,以便在任何两个鼠标事件
earlierEvent
和 currentEvent
之间,
currentEvent.screenX - earlierEvent.screenX
的值与
movementX
的所有事件的总和相等,除非指针被 用户代理 屏幕边界限制而无法更新
screenX
。
movementX
和
movementY
必须在不管指针锁定状态下更新。
当未锁定时,系统光标可以退出并重新进入 用户代理 窗口。
如果发生这种情况且 用户代理 不是操作系统鼠标移动事件的目标,
那么 用户代理 将无法知道最新的指针位置,
movementX
和
movementY
无法计算,必须设为零。
当启用指针锁定时,clientX
、clientY
、screenX
和
screenY
必须保持恒定值,仿佛在进入指针锁定后指针没有移动。
但是 movementX
和
movementY
必须继续提供指针位置的变化,类似于指针未锁定时的情况。如果鼠标持续向一个方向移动,
则 movementX
和
movementY
的值将没有限制。鼠标光标的概念将被移除,光标不会移出窗口或受到屏幕边界的限制。
movementX
和
movementY
的未初始化值必须为 0
。
当鼠标输入中断(例如鼠标光标离开窗口并在另一个位置重新进入)时,不应出现大的移动值。
如果 用户代理 在接收操作系统鼠标输入数据时遇到间隙,
则生成的下一个 mousemove
事件的
movementX
和
movementY
必须设置为 0
。这些间隙可能出现在例如当 用户代理
在窗口系统 API 接收到鼠标离开事件时。作为例外,鼠标捕获可能允许
用户代理 在光标移出窗口时继续接收鼠标事件。
WebIDLpartial dictionary MouseEventInit {
double movementX
= 0;
double movementY
= 0;
};
movementX
成员
movementY
成员
movementX
和
movementY
成员用于初始化 MouseEvent
的相应成员。
必须始终提供一个 默认解锁手势, 用于通过 退出指针锁定, 使用 用户代理 的 指针锁定目标。
如果 指针锁定目标 断开连接, 或者 用户代理、 窗口或标签页失去焦点,则必须退出指针锁定。在 活动文档 元素之间移动焦点, 包括在 浏览上下文 之间, 不会导致 退出指针锁定。 例如,使用键盘在框架或 iframe 的内容之间移动焦点不会退出。
当进入或退出全屏 [FULLSCREEN] 时, 除非指针需要启用与 用户代理 图形用户界面的交互, 使用 默认解锁手势 退出了全屏和指针锁定,或者窗口或标签页失去焦点,则不得退出指针锁定。
本节为非规范性内容。
玩家在第一/第三人称游戏中需要控制视口的方向。一个广泛使用的方法是使用鼠标移动来控制视角。 这种应用可以使用 Pointer Lock API 来允许对视口的偏航和俯仰进行完全自由的控制,即使用户没有按下鼠标按钮。 这些按钮可以用于其他操作,而鼠标移动可以持续提供导航。
三维建模应用的用户需要旋转模型。应用程序可以使用 Pointer Lock API 允许作者在拖动操作中自由旋转模型,而不会限制运动。 如果没有指针锁定,当鼠标光标受到屏幕边缘的限制时,拖动将停止提供运动数据。
同样,绝对运动的平移操作可以在不受光标/屏幕限制的单次拖动中完成,适用于大的二维图像。
在快速反应游戏中,玩家控制一个球拍将球反弹回对手,同时允许该球拍根据按下的不同鼠标按钮执行动作。 应用程序可以使用 Pointer Lock API 允许玩家快速反应,而无需担心鼠标光标离开游戏区域并点击其他系统应用程序,从而打断游戏流程。
在应用程序中修改数值时,有时用户会更喜欢通过拖动按钮手柄来增减数值。 例如,一个带有数字输入文本框和上下箭头的旋转器,可以点击或拖动来改变数值。 应用程序可以使用 Pointer Lock API 允许修改数值超过逻辑屏幕边界。这个方法同样适用于类似“快进”或“倒退”视频或音频流的控制。
一些游戏使用经典光标,但他们希望对其进行限制或控制。例如,限制在游戏范围内,或者由游戏进行移动。 锁定指针使这一点成为可能,如果应用程序创建自己的光标。但是 HTML 和 DOM 仍应可用于用户界面。 应该允许合成鼠标事件使应用定义的光标能够与 DOM 交互。例如,以下代码应允许自定义光标在指针锁定时发送点击事件:
document.addEventListener("click", function (e) {
if (e._isSynthetic)
return;
// 发送一个合成点击
var ee = document.createEvent("MouseEvents");
ee._isSynthetic = true;
x = myCursor.x;
y = myCursor.y;
ee.initMouseEvent("click", true, true, null, 1,
x + e.screenX - e.clientX,
y + e.screenY - e.clientY,
x,
y);
var target = document.elementFromPoint(x, y);
if (target)
target.dispatchEvent(ee);
});
注意,合成点击可能不被 用户代理 允许产生与非合成点击相同的默认操作。 但是,应用程序处理程序仍然可以采取行动,并使用现有的 HTML 和 DOM 机制提供用户界面。
即时战略游戏通常使用这种技术。当玩家将光标移动到视口边界时,如果他们通过鼠标移动“推动”边界, 视口将根据他们移动鼠标的距离在游戏区域内平移。当在视口范围内移动鼠标光标时,它的行为与通常系统中的行为类似。 应用程序可以选择使用指针锁定和之前的用例“与 HTML DOM UI 进行合成光标交互”来完全控制光标行为。
使用指针锁定的游戏可能希望在玩家在游戏大厅准备时拥有传统的用户界面和系统光标。 游戏通常在所有玩家准备好后经过短时间的计时器后开始。理想情况下,游戏可以在不需要 用户激活的情况下切换到指针锁定模式。 玩家应能够无缝地从游戏大厅过渡到游戏导航。
游戏门户和其他网站(如 Facebook 和 Google Plus)托管供用户玩的游戏。 这些游戏可能托管在与门户网站不同的来源。嵌入式游戏应能够在非全屏模式下锁定指针。
本节为非规范性内容。
安全问题:
应对措施:
建议:
指针锁定是某些应用程序类型所需的用户交互模式,但如果被恶意使用,可能会带来可用性问题。 攻击者可能会移除用户在其系统上控制鼠标光标的能力。用户代理 将通过始终提供退出机制、告知用户如何退出并限制指针锁定的进入方式来防止这种情况。
用户代理 将根据设备或用户选项自行确定适当的策略,这些策略可能因设备或用户选项而有所不同。
本节为非规范性内容。
Mouse Capture [MDN-SETCAPTURE] 处理了鼠标拖动手势期间低安全风险的鼠标事件目标锁定。 指针锁定移除了光标的概念,并将所有事件指向给定的目标。它们是相关的,但不同。
如果浏览器实现了两者,支持功能组合是合理的:自动释放锁定的安全简化功能,以及对鼠标输入的完全控制和移除系统光标的增强功能。 该安全特性允许应用程序在拖动事件期间仅需短暂的指针锁定时,更宽松地使用该功能。
这种功能在该规范的初始版本中被省略,因为它仅帮助了窗口模式下的小众用例,但我们仍然没有解决主要用例的实现方案。 而且,要实现这一点,浏览器必须同时实现这两者,而目前没有浏览器这样做。尚不清楚该功能应属于 .lock 还是 .setCapture。 如果两者都实现了,任何 API 都可以很容易地扩展以提供混合功能。
即使在非锁定状态下,鼠标移动的增量值也是有用的。根据锁定状态改变 .client 或 .screen 的含义还会导致代码中容易出现错误, 特别是在不仔细监控锁定状态时。
当指针锁定时,“wheel”事件应与“mousemove”事件一样发送到 指针锁定目标 元素。 与 DOM 3 “wheel”事件中定义的 .deltaX/Y/Z 存在命名冲突。
提供更精细控制的方法是合理的。例如,“通过将鼠标光标移动到视口边界来进行视口平移”这一用例并不需要隐藏光标, 只需限制光标并始终提供增量值即可。此外,本规范定义了从系统鼠标光标移动中获取的运动增量, 该增量包含操作系统对鼠标移动数据的过滤和加速。应用程序可能希望访问更原始的移动数据形式, 即在适合鼠标光标的调整之前的数据。此外,原始数据可能提供比像素级别更高的移动精度,以及更高频率的更新。 提供原始增量运动不需要用户的特殊权限或模式,对于不需要限制光标的一些应用程序来说,可能会减少所需的安全屏障和提示。
推迟这种精细控制方法的原因有两个。首先是关于鼠标移动数据的单位问题。 本规范定义了 .movementX/Y 与鼠标未锁定时的 .screenX/Y 变化相同的值。各个 用户代理 和操作系统可以轻松满足这一要求,并为应用程序开发者和用户提供一致的体验。 此外,用户已预先配置了硬件输入和操作系统选项,从而使得系统鼠标光标控制更为舒适。 通过将 .movementX/Y 规定为与鼠标锁定 API 应用程序相同的单位,所有用户都可以立即使用这些应用程序,因为他们已经调整了他们的偏好。
其次,实现精细控制方法的运动数据和光标限制更为困难。捆绑这些功能为实现提供了自由, 使其可以根据每个操作系统使用各种技术,并且更容易实现。在主要桌面平台(Windows、Mac OS X、Linux)上没有直接的 API 来将光标限制到特定的矩形区域,并且尚未开发出通过例如使用 不可见窗口 或 Xlib 进行原型构建来演示这一行为。未加速的增量值已经提出通过读取原始人机界面设备(HID)数据来访问。 例如,Windows 上的 WM_INPUT 消息,以及 Mac OS X / Linux 上的 USB 设备 API。 挑战在于解释和规范单位为一致且可指定的尺度。此外,迄今为止考虑的大多数 API 都仅限于 USB 设备。
考虑到当前规范的指针锁定 API 在未来实现这些精细控制的增量和限制功能时可以轻松继续支持, 添加这些功能在未来是合理的。
捆绑的 API 选择了实现的实用性,因为支持了预期的用例,并且不会与此处讨论的未来改进发生冲突。
还没有,原因与上一个问题相同:“为什么将所有功能(隐藏光标、提供鼠标增量)捆绑在一起,而不是使用 CSS 隐藏光标、 始终提供增量值并提供限制光标移动到网页某部分的 API?”。
在指针锁定时,许多鼠标事件仍然相关,例如 click、mousedown 等。它们都共享相同的事件数据结构 MouseEvent。 如果通过一个新数据结构报告移动数据,那么将需要一个新事件来报告增量移动。 新的数据结构将与 MouseEvent 有许多相似之处,例如按钮和修改键状态的便利性。 在处理 click、down 和 up 事件时是否使用现有的 mousedown 和 mouseup? 如果是,它们将提供无用的 .clientX/Y 和 .screenX/Y 数据,但缺乏包含当前运动数据的便利性。 否则,当鼠标锁定时,将需要新的事件。
此外,即使鼠标未锁定,movementX/Y 也是方便的。本规范要求即使光标存在,运动成员始终有效。 这减少了在应用程序希望利用鼠标的增量运动时,跟踪最后的光标状态和 mouseover/mouseout 过渡所需的代码。
将 movementX/Y 添加到 MouseEvent 的唯一缺点似乎是在指针锁定时 clientX/Y 和 screenX/Y 中的未使用值。 这似乎不是一个重要问题。
因此,选择了向 MouseEvent 添加 movementX/Y 这一最小变更,以减少 API 和实现的复杂性。
考虑一个通过移动鼠标光标控制 3D 视图的游戏,同时用户仍可以通过文本控制台与其他用户聊天。 应用程序接受文本输入到与分派鼠标事件的元素不同的元素是合理的。这类似于在页面上填写表单时接收 mousemove 事件的预先行为。
与标记为非规范的章节一样,本规范中的所有作者指南、图表、示例和注释都是非规范的。除此之外,本规范中的所有内容都是规范性的。
本规范定义了一些一致性标准,适用于单个产品:用户代理,即实现了其中包含的接口的用户代理。
本节为非规范性内容。
非常感谢众多对本规范讨论做出贡献的人们:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: