diff --git a/files/zh-cn/web/api/pointer_lock_api/index.md b/files/zh-cn/web/api/pointer_lock_api/index.md index 01f9f5b021c2ab..f045793de86648 100644 --- a/files/zh-cn/web/api/pointer_lock_api/index.md +++ b/files/zh-cn/web/api/pointer_lock_api/index.md @@ -1,214 +1,253 @@ --- -title: Pointer Lock API +title: 指针锁定 API slug: Web/API/Pointer_Lock_API +l10n: + sourceCommit: 802b6063046dffb7634d2138aadcd92cb22ed40c --- -{{DefaultAPISidebar("Pointer Lock API")}}{{ SeeCompatTable() }} +{{DefaultAPISidebar("Pointer Lock API")}} -**指针锁定**(以前叫做鼠标锁定) 提供了一种输入方法,这种方法是基于鼠标随着时间推移的运动的(也就是,deltas),而不仅是鼠标光标的绝对位置。通过它可以访问原始的鼠标运动,把鼠标事件的目标锁定到一个单独的元素,这就消除了鼠标在一个单独的方向上到底可以移动多远这方面的限制,并从视图中删去光标。 +**指针锁定 API**(Pointer Lock API,以前叫做 _Mouse Lock API_)提供了一种基于鼠标随着时间推移(也就是增量)的运动的输入方法,而不仅是鼠标光标在视口中的绝对位置。通过它可以访问原始的鼠标运动,把鼠标事件的目标锁定到一个单独的元素,这就消除了鼠标在一个单独的方向上到底可以移动多远这方面的限制,并从视图中删去光标。例如,它是第一人称 3D 游戏的理想选择。 -这个 API 对于需要大量的鼠标输入来控制运动,旋转物体,以及更改项目的应用程序来说非常有用。对高度视觉化的应用程序尤其重要,例如那些使用第一人称视角的应用程序,以及 3D 视图和建模。 +不仅如此,该 API 对于需要大量鼠标输入来控制移动、旋转对象和更改条目的任何应用程序都非常有用,例如,允许用户通过移动鼠标来控制视角,而无需单击任何按钮。然后释放按钮以执行其他操作。其他示例包括用于查看地图或卫星图像的应用程序。 -举例来说,你可以创建让你的用户简单地通过移动鼠标而不需要点击任何按钮就可以控制视角的应用。那么这些按钮就可以被用作其他动作。这类鼠标输入对于查看地图,卫星图像,或者第一人称场景(例如在一个游戏中或者一个全景视频中)是非常方便使用的。 - -即使在光标移到浏览器或者屏幕区域之外,指针锁定也能够让你访问鼠标事件。例如,你的用户可以通过不断地移动鼠标来持续旋转或操纵一个 3D 模型。如果没有指针锁定的话,这些旋转或操纵会在指针到达浏览器或者屏幕边缘的那一刻停止。尤其是游戏玩家将会因为此功能而兴奋不已,因为他们可以疯狂地点击按钮,来回地滑动鼠标光标,而不必担心离开了游戏区域,进而不小心误点到另外一个应用程序上,结果将鼠标焦点移离了游戏。杯具了! +指针锁定让你即使光标超出浏览器或屏幕的边界也能访问鼠标事件。例如,你的用户可以通过无休止地移动鼠标来继续旋转或操作 3D 模型。如果没有指针锁定,旋转或操作会在指针到达浏览器或屏幕的边缘时停止。游戏玩家现在可以点击按钮并来回滑动鼠标光标,而不必担心离开游戏区域并意外点击另一个应用程序而导致鼠标焦点从游戏中移开。 ## 基本概念 -指针锁定和[鼠标捕获](/zh-CN/DOM/element.setCapture)有关。鼠标捕获在一个鼠标被拖曳时可以向一个目标元素持续传递有关事件,但是当鼠标按钮被放开时就会停止。指针锁定和鼠标捕获在以下方面有所不同: +指针锁定和[指针捕获](/zh-CN/docs/Web/API/Pointer_events#指针捕捉)有关。指针捕获在鼠标被拖曳时可以向一个目标元素持续传递有关事件,但是当鼠标按钮被放开时就会停止。指针锁定和指针捕获在以下方面有所不同: - 它是持久性的。指针锁定不释放鼠标,直到作出一个显式的 API 调用或是用户使用一个专门的释放手势。 - 它不局限于浏览器或者屏幕边界。 - 它持续发送事件,而不管鼠标按钮状态如何。 - 它隐藏光标。 -## 示例 +## 方法/属性概述 -下面是一个如何在你的网页中设置指针锁定的示例。 +本小节对与指针锁定规范相关的每个属性和方法进行了简要说明。 -```js - -
- +要禁用操作系统级鼠标加速并访问原始鼠标输入,你可以将 `unadjustedMovement` 设置为 `true`: + +```js +canvas.addEventListener("click", async () => { + await canvas.requestPointerLock({ + unadjustedMovement: true, + }); +}); ``` -## 方法/属性 概述 +## 处理 requestPointerLock() 的 promise 版本和非 promise 版本 + +上述代码片段在不支持基于 promise 的 `requestPointerLock()` 版本或 `unadjustedMovement` 选项的浏览器中仍可正常工作——允许在不返回 promise 的函数前面使用 [`await`](/zh-CN/docs/Web/JavaScript/Reference/Operators/await) 运算符,并且选项对象将在不支持的浏览器中被忽略。 -Pointer lock API,和 Fullscreen API 类似,通过添加新方法来扩展 DOM 元素,`requestPointerLock`, 目前还是厂商前缀。按下面这样来写: +但是,这可能会造成混淆,并且有其他潜在的副作用(例如,尝试使用 `requestPointerLock().then()` 会在不支持的浏览器中引发错误),因此你可能需要使用以下代码明确处理此问题: ```js -element.webkitRequestPointerLock(); // Chrome +function requestPointerLockWithUnadjustedMovement() { + const promise = myTargetElement.requestPointerLock({ + unadjustedMovement: true, + }); + + if (!promise) { + console.log("不支持禁用鼠标加速"); + return; + } -element.mozRequestPointerLock(); // Firefox + return promise + .then(() => console.log("指针被锁定")) + .catch((error) => { + if (error.name === "NotSupportedError") { + // 有些平台可能不支持未调整的移动。你可以重新请求常规指针锁定。 + return myTargetElement.requestPointerLock(); + } + }); +} ``` -目前 `requestPointerLock` 的实现还是和 `requestFullScreen` 以及 Fullscreen API 紧紧地绑在一起的。一个元素在能够被指针锁定之前,必须首先进入全屏模式。就像上面演示的那样,锁定指针的过程是异步的,使用 (`pointerlockchange`, `pointerlockerror`) 事件来表明请求是成功还是失败了。这和 Fullscreen API 的工作方式是一致的,它使用 `requestFullScreen` 方法,以及 `fullscreenchange` 和 `fullscreenerror` 事件。 +### pointerLockElement 和 exitPointerLock() + +指针锁定 API 还扩展了 {{domxref("Document")}} 接口,添加了一个新属性和一个新方法: + +- {{domxref("Document.pointerLockElement","pointerLockElement")}} 用于访问当前锁定的元素(如果有)。 +- {{domxref("Document.exitPointerLock","exitPointerLock()")}} 用于退出指针锁定。 -Pointer lock API 还扩展了 `document` 接口,添加了一个新的属性和一个新的方法。新的属性被用于访问当前被锁定的元素(如果有的话),并被命名为 `pointerLockElement`,目前也使用厂商前缀。 `document` 添加的新方法是 `exitPointerLock` ,顾名思义,它是用来退出指针锁定的。 +{{domxref("Document.pointerLockElement","pointerLockElement")}} 属性可用于确定任何元素当前是否被指针锁定(例如,进行布尔检查),也可用于获取对锁定元素的引用(如果有)。 -`pointerLockElement` 属性适用于确定当前是否有被指针锁定的元素(例如,用来做一个布尔检查),以及当有元素被锁定时获取该元素的一个引用。下面是这两种用法的一个例子: +以下是使用 `pointerLockElement` 的示例: ```js -document.pointerLockElement = - document.pointerLockElement || - document.mozPointerLockElement || - document.webkitPointerLockElement; - -// 1) 用于布尔检查 -- 我们被指针锁定了吗? -if (!!document.pointerLockElement) { - // 指针被锁定 +if (document.pointerLockElement === canvas) { + console.log("指针锁定状态现已锁定"); } else { - // 指针未被锁定 + console.log("指针锁定状态现已解锁"); } +``` -// 2) 用于访问指针锁定的元素 -if (document.pointerLockElement === someElement) { - // someElement 当前被指针锁定 -} +{{domxref("Document.exitPointerLock()")}} 方法用于退出指针锁定,与 {{domxref("Element.requestPointerLock","re​​questPointerLock")}} 一样,使用 {{domxref("Document/pointerlockchange_event", "pointerlockchange")}} 和 {{domxref("Document/pointerlockerror_event", "pointerlockerror")}} 事件异步运行,你将在下面看到更多相关内容。 + +```js +document.exitPointerLock(); ``` -`document` 的 `exitPointerLock` 方法被用来退出指针锁定,而且和 requestPointerLock 一样,使用 `pointerlockchange` 和 `pointerlockerror`事件以异步方式工作: +## pointerlockchange 事件 + +当指针锁定状态改变时——例如,当调用 {{domxref("Element.requestPointerLock","requestPointerLock()")}} 或 {{domxref("Document.exitPointerLock","exitPointerLock()")}},用户按下 ESC 键,等等——{{domxref("Document/pointerlockchange_event", "pointerlockchange")}} 事件在 `document` 上触发。这是一个不包含任何的额外数据的简单事件。 ```js -document.exitPointerLock = - document.exitPointerLock || - document.mozExitPointerLock || - document.webkitExitPointerLock; - -function pointerLockChange() { - document.pointerLockElement = - document.pointerLockElement || - document.mozPointerLockElement || - document.webkitPointerLockElement; - - if (!!document.pointerLockElement) { - console.log("目前还是被锁定。"); +document.addEventListener("pointerlockchange", lockChangeAlert, false); + +function lockChangeAlert() { + if (document.pointerLockElement === canvas) { + console.log("指针锁定状态现已锁定"); + // 做一些有用的事情作为回应 } else { - console.log("已经退出锁定。"); + console.log("指针锁定状态现已解锁"); + // 做一些有用的事情作为回应 } } +``` -document.addEventListener("pointerlockchange", pointerLockChange, false); -document.addEventListener("mozpointerlockchange", pointerLockChange, false); -document.addEventListener("webkitpointerlockchange", pointerLockChange, false); +## pointerlockerror 事件 -// 试图解除锁定 -document.exitPointerLock(); +当调用 {{domxref("Element.requestPointerLock","requestPointerLock()")}} 或 {{domxref("Document.exitPointerLock","exitPointerLock()")}} 而引发错误时, {{domxref("Document/pointerlockerror_event", "pointerlockerror")}} 事件在 `document` 上触发。这是一个简单事件所以不包含任何的额外数据。 + +```js +document.addEventListener("pointerlockerror", lockError, false); + +function lockError(e) { + alert("指针锁定失败"); +} ``` -## pointerlockchange 事件 +## 鼠标事件的扩展 -当指针锁定状态改变时 - 例如,当调用 `requestPointerLock`, `exitPointerLock`,用户按下 ESC 键,等等。— `pointerlockchange` 事件被分发到 `document`。这是一个简单事件所以不包含任何的额外数据。 +指针锁定 API 扩展了普通的 {{domxref("MouseEvent")}} 接口,使其具有 movement 属性。鼠标事件的两个新属性——{{domxref("MouseEvent.movementX","movementX")}} 和 {{domxref("MouseEvent.movementY","movementY")}}——提供鼠标位置的变化。参数的值与 {{domxref("MouseEvent")}} 属性值 {{domxref("MouseEvent.screenX","screenX")}} 和 {{domxref("MouseEvent.screenY","screenY")}} 之间的差值相同,它们存储在两个后续 {{domxref("Element/mousemove_event", "mousemove")}} 事件 `eNow` 和 `ePrevious` 中。换句话说,指针锁定参数 `movementX = eNow.screenX - ePrevious.screenX`。 -> [!NOTE] -> 该事件目前在 Firefox 中使用前缀的格式是 `mozpointerlockchange` ,在 Chrome 中是 `webkitpointerlockchange`。 +### 锁定状态 -## pointerlockerror 事件 +当指针锁定被启动之后,标准的 {{domxref("MouseEvent")}} 属性 {{domxref("MouseEvent.clientX","clientX")}}、{{domxref("MouseEvent.clientY","clientY")}}、{{domxref("MouseEvent.screenX","screenX")}} 和 {{domxref("MouseEvent.screenY","screenY")}} 属性保持不变,就像鼠标未移动一样。{{domxref("MouseEvent.movementX","movementX")}} 和 {{domxref("MouseEvent.movementY","movementY")}} 属性继续提供鼠标的位置变化。如果鼠标持续朝一个方向移动,则 {{domxref("MouseEvent.movementX","movementX")}} 和 {{domxref("MouseEvent.movementY","movementY")}} 值不受限制。鼠标光标的概念不存在,并且光标不能移出窗口或被屏幕边缘夹住。 -当调用 `requestPointerLock` 或 `exitPointerLock`而引发错误时, `pointerlockerror` 事件被分发到 `document`。这是一个简单事件所以不包含任何的额外数据。 +### 未锁定状态 -> [!NOTE] -> 该事件目前在 Firefox 中被加上前缀为 `mozpointerlockerror` ,在 Chrome 中为 `webkitpointerlockerror`。 +无论鼠标是否处于锁定状态,参数 {{domxref("MouseEvent.movementX","movementX")}} 和 {{domxref("MouseEvent.movementY","movementY")}} 均有效,并且即使鼠标解锁后也可用,以方便使用。 + +当鼠标解锁时,系统光标可以退出并重新进入浏览器窗口。如果发生这种情况,可以将 {{domxref("MouseEvent.movementX","movementX")}} 和 {{domxref("MouseEvent.movementY","movementY")}} 设置为零。 + +## 简单示例演示 -## 鼠标事件扩展 +我们编写了一个[指针锁定演示](https://mdn.github.io/dom-examples/pointer-lock/)([查看源代码](https://github.com/mdn/dom-examples/tree/main/pointer-lock)),向你展示如何使用它来设置一个简单的控制系统。此演示使用 JavaScript 在 {{ htmlelement("canvas") }} 元素上绘制一个球。当你单击画布时,指针锁定将用于移除鼠标指针,并允许你直接使用鼠标移动球。让我们看看它是如何工作的。 -Pointer lock API 使用 movement 属性扩展了标准的 `MouseEvent`。 +我们在画布上设置 x 和 y 的初始位置: -```webidl -partial interface MouseEvent { - readonly attribute long movementX; - readonly attribute long movementY; -}; +```js +let x = 50; +let y = 50; +``` + +接下来,我们设置一个事件监听器,当单击画布时,在画布上运行 `requestPointerLock()` 方法,从而启动指针锁定。`document.pointerLockElement` 检查是为了查看是否已经有一个活动的指针锁定——如果已经有指针锁定,我们不想每次单击画布时都继续调用 `requestPointerLock()`。 + +```js +canvas.addEventListener("click", async () => { + if (!document.pointerLockElement) { + await canvas.requestPointerLock({ + unadjustedMovement: true, + }); + } +}); ``` > [!NOTE] -> movement 属性目前在 Firefox 中被加上前缀为 `.mozMovementX` 和 `.mozMovementY` , 在 Chrome 中为`.webkitMovementX` 和 `.webkitMovementY`。 +> 上述代码片段在不支持 `requestPointerLock()` 的 promise 版本的浏览器中有效。请参阅[处理 requestPointerLock() 的 promise 版本和非 promise 版本](#处理_requestpointerlock_的_promise_版本和非_promise_版本)了解详细说明。 -鼠标事件的两个新参数—`movementX` 和 `movementY`—提供了鼠标位置的变化情况。这两个参数的值,等于两个[`MouseEvent`](/zh-CN/DOM/MouseEvent) 属性( `screenX` 和 `screenY)`之间值的变化程度,这些 MouseEvent 属性被存储在两个连续的鼠标移动事件( `eNow` 和 `ePrevious`)中。换言之,指针锁定参数 `movementX = eNow.screenX - ePrevious.screenX`。(译注:不存在名为 eNow 或 ePrevious 的事件或属性,eNow 代指当前的鼠标移动事件,ePrevious 代指前一个鼠标移动事件) +现在来看看专用的指针锁定事件监听器:`pointerlockchange`。当这种情况发生时,我们运行一个名为 `lockChangeAlert()` 的函数来处理变化。 -### 锁定状态 +```js +document.addEventListener("pointerlockchange", lockChangeAlert, false); +``` -当指针锁定被启动之后,正常的 `MouseEvent` 属性 `clientX`, `clientY`, `screenX`, 和 `screenY` ,保持不变,就像鼠标没有在移动一样。 `movementX` 和 `movementY` 属性持续提供鼠标的位置变化。如果鼠标在一个方向上持续移动,`movementX` 和 `movementY`的值是没有限制的。不存在鼠标光标的概念,而且光标无法移到窗口之外,而且也不会被屏幕边缘所固定。 +此函数检查 `pointerLockElement` 属性以查看它是否是我们的画布。如果是,它会附加一个事件侦听器,以使用 `updatePosition()` 函数处理鼠标移动。如果不是,它会再次删除事件侦听器。 -### 未锁定状态 +```js +function lockChangeAlert() { + if (document.pointerLockElement === canvas) { + console.log("指针锁定状态现已锁定"); + document.addEventListener("mousemove", updatePosition, false); + } else { + console.log("指针锁定状态现已解锁"); + document.removeEventListener("mousemove", updatePosition, false); + } +} +``` + +`updatePosition()` 函数会更新球在画布上的位置(`x` 和 `y`),同时还包含 `if ()` 语句,用于检查球是否已经超出画布的边缘。如果超出,则让球绕到另一侧的边缘。它还会检查之前是否已调用 [`requestAnimationFrame()`](/zh-CN/docs/Web/API/Window/requestAnimationFrame),如果已调用,则根据需要再次调用,并调用 `canvasDraw()` 函数来更新画布场景。还设置了一个跟踪器,用于将 X 和 Y 值写入屏幕,以供参考。 + +```js +const tracker = document.getElementById("tracker"); + +let animation; +function updatePosition(e) { + x += e.movementX; + y += e.movementY; + if (x > canvas.width + RADIUS) { + x = -RADIUS; + } + if (y > canvas.height + RADIUS) { + y = -RADIUS; + } + if (x < -RADIUS) { + x = canvas.width + RADIUS; + } + if (y < -RADIUS) { + y = canvas.height + RADIUS; + } + tracker.textContent = `X 坐标:${x},Y 坐标:${y}`; + + if (!animation) { + animation = requestAnimationFrame(() => { + animation = null; + canvasDraw(); + }); + } +} +``` -无论鼠标锁定状态是怎样的, `movementX` 和 `movementY` 参数一直有效,并且为了方便起见,甚至在未锁定状态也是有效的。 +`canvasDraw()` 函数在当前 `x` 和 `y` 位置绘制圆: -当鼠标被解除锁定,系统光标可以退出并重新进入浏览器窗口。如果发生这种情况,`movementX` 和 `movementY` 可能会被设置成 0。 +```js +function canvasDraw() { + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#f00"; + ctx.beginPath(); + ctx.arc(x, y, RADIUS, 0, degToRad(360), true); + ctx.fill(); +} +``` ## iframe 的限制 -指针锁定一次只能锁定一个 iframe。如果你锁定了一个 iframe,你不能试图锁定另外一个 iframe 然后把目标转移到这个 iframe 上;指针锁定将会出错。为了避免这一问题,首先解锁那个锁定的 iframe,然后再锁定另外一个。 +指针锁定一次只能锁定一个 {{htmlelement("iframe")}}。如果你锁定一个 `