Skip to main content

Pointer Lock API - 提供基于鼠标随时间移动的输入方法

Pointer Lock API(指针锁定 API)(以前称为 Mouse Lock API(鼠标锁定 API))提供基于鼠标随时间移动(即增量)的输入方法,而不仅仅是鼠标指针在视口中的绝对位置。它使您可以访问原始鼠标移动,将鼠标事件的目标锁定到单个元素,消除对鼠标在单个方向上移动的距离的限制,并从视图中删除光标。例如,它非常适合第一人称 3D 游戏。

不仅如此,该 API 对于需要大量鼠标输入来控制移动、旋转对象和更改条目的任何应用程序都很有用,例如,允许用户通过移动鼠标来控制视角,而无需单击任何按钮。然后释放按钮用于其他操作。其他实例包括用于查看地图或卫星图像的应用程序。

即使光标越过浏览器或屏幕的边界,指针锁定也可让您访问鼠标事件。例如,您的用户可以通过无休止地移动鼠标来继续旋转或操作 3D 模型。如果没有指针锁定,旋转或操作会在指针到达浏览器或屏幕边缘的那一刻停止。游戏玩家现在可以点击按钮并来回滑动鼠标光标,而不必担心离开游戏区域,不小心点击另一个应用程序,从而将鼠标焦点从游戏中移开。

基本概念

指针锁定与鼠标捕获有关。鼠标捕获在拖动鼠标时向目标元素提供持续的事件传递,但在释放鼠标按钮时停止。指针锁定与鼠标捕捉的不同之处在于:

  • 它是持久的:在进行显式 API 调用或用户使用特定的释放手势之前,指针锁定不会释放鼠标。
  • 它不受浏览器或屏幕边界的限制。
  • 无论鼠标按钮状态如何,它都会继续发送事件。
  • 它会隐藏光标。

方法 / 属性概述

本节简要说明与指针锁规范相关的每个属性和方法。

requestPointerLock()

Pointer lock API 类似于 Fullscreen API,通过添加一个新方法 requestPointerLock() 来扩展 DOM 元素。由于它最近取消了前缀,您目前可以像这样声明它,例如,如果您想请求对 canvas 元素的指针锁定:

canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock;

canvas.requestPointerLock()

注意:如果用户已通过默认解锁手势退出指针锁定,或者该文档之前尚未进入指针锁定,则在 requestPointerLock 成功之前,文档必须接收到由于参与手势而生成的事件,才可进入指针锁定。(来自 https://w3c.github.io/pointerlock/#extensions-to-the-element-interface

pointerLockElement 和 exitPointerLock()

指针锁 API 还扩展了 Document 接口,添加了新属性和新方法。新属性用于访问当前锁定的元素(如果有的话)。属性名称为 pointerLockElementDocument 上的新方法是 exitPointerLock() ,顾名思义,用于退出指针锁。

pointerLockElement 属性可用于确定当前是否有任何元素被指针锁定以及获取对锁定元素的引用(如果有的话)。

下面是一个使用 pointerLockElement 的例子:

if(document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas) {
console.log('指针锁状态现已锁定');
} else {
console.log('指针锁状态现已解锁');
}
Document.exitPointerLock() 方法用于退出指针锁定,与 requestPointerLock 一样,会异步调用 pointerlockchangepointerlockerror 事件,您将在下面看到更多相关信息。
document.exitPointerLock = document.exitPointerLock    ||
document.mozExitPointerLock;

// 尝试解锁
document.exitPointerLock();

pointerlockchange 事件

当指针锁状态发生变化时 —— 例如调用 requestPointerLock(), exitPointerLock(), 用户按下 ESC 键等 —— pointerlockchange 事件会被分派到 document。这是一个简单的事件,不包含额外的数据。

if ("onpointerlockchange" in document) {
document.addEventListener('pointerlockchange', lockChangeAlert, false);
} else if ("onmozpointerlockchange" in document) {
document.addEventListener('mozpointerlockchange', lockChangeAlert, false);
}

function lockChangeAlert() {
if(document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas) {
console.log('指针锁定状态现已锁定');
// 做一些处理
} else {
console.log('指针锁定状态现已解锁');
// 做一些处理
}
}

pointerlockerror 事件

当调用 requestPointerLock()exitPointerLock() 导致错误时,pointerlockerror 事件会被分派到 document 。这是一个简单的事件,不包含额外的数据。

document.addEventListener('pointerlockerror', lockError, false);
document.addEventListener('mozpointerlockerror', lockError, false);

function lockError(e) {
alert("Pointer lock failed");
}

注意: 在 Firefox 50 之前,上述事件在 Firefox 中要加上 moz 为前缀。

鼠标事件的扩展

指针锁定 API 为MouseEvent 接口增加了移动相关的属性。鼠标事件的两个新属性 — movementXmovementY — 提供鼠标位置的变化。参数的值与 MouseEventscreenXscreenY 属性的值之差相同,它们存储在两个后续的 mousemove 事件的 eNowePrevious 属性中。换句话说,指针锁定参数 movementX = eNow.screenX - ePrevious.screenX

锁定状态

启用指针锁定时,标准 MouseEvent 属性 clientXclientYscreenXscreenY 保持不变,就好像鼠标没有移动一样。movementXmovementY 属性继续随鼠标的位置变化。如果鼠标在一个方向上连续移动,则 movementXmovementY 的值没有限制。鼠标光标的概念不存在,光标不会移出窗口或被屏幕边缘夹住。

解锁状态

参数 movementXmovementY 无论鼠标是否锁定状态都有效,为了方便,即使在解锁时也可用。

当鼠标解锁时,系统光标可以退出并重新进入浏览器窗口。如果发生这种情况,movementXmovementY 会设置为零。

简单实例演练

我们已经写了一个简单的指针锁定演示 来告诉你如何使用它来设置一个简单的控制系统(查看源代码)。该演示如下所示:

黑色背景上的红色圆圈。

此演示使用 JavaScript 在 <canvas> 元素上绘制一个球。当您单击画布时,将使用指针锁定来移除鼠标指针并允许您直接使用鼠标移动球。让我们看看这是如何工作的。

我们在画布上设置初始 x 和 y 位置:

var x = 50;
var y = 50;

指针锁定方法目前是有前缀的,所以接下来我们将针对不同的浏览器的实现对它们进行处理。

canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock;

document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock;

现在我们设置了一个事件监听器,当它被点击时,它会在画布上运行 requestPointerLock() 方法,它将启动指针锁定。

canvas.onclick = function() {
canvas.requestPointerLock();
}

现在是指针锁定事件侦听器:pointerlockchange。当它触发时,我们会运行一个名为 lockChangeAlert() 的函数来处理更改。

// 为不同的浏览器处理指针锁定状态变化事件

document.addEventListener('pointerlockchange', lockChangeAlert, false);
document.addEventListener('mozpointerlockchange', lockChangeAlert, false);

这个函数检查 pointLockElement 属性,看它是否是我们的画布。如果是,它会附加一个事件侦听器以使用 updatePosition() 函数处理鼠标移动。如果不是,它会再次删除事件侦听器。

function lockChangeAlert() {
if (document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas) {
console.log('指针锁定状态现已锁定');
document.addEventListener("mousemove", updatePosition, false);
} else {
console.log('指针锁定状态现已解锁');
document.removeEventListener("mousemove", updatePosition, false);
}
}

updatePosition() 函数更新球在画布上的位置(xy),还包括 if() 语句来检查球是否离开了画布的边缘。如果是这样,它会让球绕到对面的边缘。它还包括检查之前是否调用了 requestAnimationFrame(),如果是,则根据需要再次调用,并调用 canvasDraw() 函数更新画布场景。还设置了跟踪器以将 X 和 Y 值写出到屏幕上,以供参考。

var tracker = document.getElementById('tracker');

var 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(function() {
animation = null;
canvasDraw();
});
}
}

canvasDraw() 函数在当前的 xy 位置绘制一个球:

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 中默认可用指针锁定,但 “沙盒化” iframe 会阻止指针锁定。可以设置 <iframe sandbox="allow-pointer-lock"> 属性来避免这种限制。预计很快 Chrome 就会支持该功能。

规范

规范
Pointer Lock

桌面浏览器兼容性

特性ChromeEdgeFirefoxInternet ExplorerOperaSafari
基础支持

371

22 — 38webkit

131

50

14 — 50moz

不支持

242

15 — 25webkit

10.1
options.unadjustedMovement parameter9292不支持不支持78不支持

移动浏览器兼容性

特性AndroidChrome for AndroidEdge mobileFirefox for AndroidIE mobileOpera AndroidiOS Safari
基础支持

371

4.4 — 38webkit

371

25 — 38webkit

未知

50

14 — 50moz

未知

243

14 — 25webkit

不支持
options.unadjustedMovement parameter9292未知不支持未知65不支持

1. From version 92, returns a promise instead of undefined. The behavior reflects a proposed specification change.

2. From version 78, returns a promise instead of undefined. The behavior reflects a proposed specification change.

3. From version 65, returns a promise instead of undefined. The behavior reflects a proposed specification change.

4. From version 16, returns a promise instead of undefined. The behavior reflects a proposed specification change.

相关链接