Pointer Lock API
一、Pointer Lock 概述
1.1 什么是 Pointer Lock
Pointer Lock API(曾名 Mouse Lock API)是一种输入机制,基于鼠标移动时间(deltas)而非绝对位置获取输入。它将鼠标事件锁定到单个元素,消除边界限制并隐藏光标。
// 请求指针锁定
canvas.addEventListener('click', async () => {
await canvas.requestPointerLock();
});
1.2 与普通鼠标捕获的区别
| 特性 | Pointer Lock | 普通鼠标捕获 |
|---|---|---|
| 光标可见性 | 完全隐藏 | 可选择隐藏 |
| 移动边界 | 无限制 | 可限制在元素内 |
| movementX/Y | 累积增量 | 相对最后位置 |
| 用途 | 游戏、3D 应用 | 拖拽、绘图 |
// 普通鼠标捕获:光标仍在,事件重定向
element.addEventListener('mouseover', (e) => {
// 事件重定向到 element
});
// Pointer Lock:完全锁定,光标消失
canvas.requestPointerLock();
二、核心 API
2.1 requestPointerLock
canvas.addEventListener('click', async () => {
try {
await canvas.requestPointerLock();
console.log('Pointer locked');
} catch (error) {
console.error('Failed to lock pointer:', error);
}
});
// 带选项
canvas.requestPointerLock({
unadjustedMovement: true // 禁用 OS 级鼠标加速
});
2.2 退出锁定
document.exitPointerLock();
// 监听退出
document.addEventListener('pointerlockchange', () => {
if (document.pointerLockElement === null) {
console.log('Pointer unlocked');
}
});
2.3 pointerLockElement 属性
if (document.pointerLockElement === canvas) {
console.log('Pointer is locked to canvas');
} else {
console.log('Pointer is not locked');
}
三、鼠标移动事件
3.1 movementX 和 movementY
Pointer Lock 期间,mousemove 事件的 movementX 和 movementY 属性表示自上次事件以来的累积鼠标移动:
let x = 0, y = 0;
document.addEventListener('mousemove', (e) => {
// 累积移动量
x += e.movementX;
y += e.movementY;
// 更新相机/玩家位置
camera.rotation.x += e.movementY * 0.002;
camera.rotation.y += e.movementX * 0.002;
});
3.2 3D 相机控制
class FPSCamera {
constructor(element) {
this.element = element;
this.pitch = 0; // 垂直旋转
this.yaw = 0; // 水平旋转
this.sensitivity = 0.002;
document.addEventListener('mousemove', this.onMouseMove.bind(this));
}
onMouseMove(e) {
if (document.pointerLockElement !== this.element) return;
this.yaw -= e.movementX * this.sensitivity;
this.pitch -= e.movementY * this.sensitivity;
// 限制垂直旋转
this.pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.pitch));
}
lookAt(target) {
// 应用旋转到相机
}
}
四、实际应用
4.1 3D 场景查看器
class SceneViewer {
constructor(canvas) {
this.canvas = canvas;
this.isLocked = false;
canvas.addEventListener('click', () => this.enter());
document.addEventListener('pointerlockchange', () => this.onLockChange());
document.addEventListener('mousemove', (e) => this.onMouseMove(e));
}
async enter() {
await this.canvas.requestPointerLock();
}
onLockChange() {
this.isLocked = document.pointerLockElement === this.canvas;
this.canvas.style.cursor = this.isLocked ? 'none' : 'pointer';
}
onMouseMove(e) {
if (!this.isLocked) return;
// 更新场景旋转
this.scene.rotation.y -= e.movementX * 0.01;
this.scene.rotation.x -= e.movementY * 0.01;
}
}
4.2 绘图应用
class DrawingApp {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.isDrawing = false;
canvas.addEventListener('click', async () => {
await canvas.requestPointerLock();
});
document.addEventListener('mousemove', (e) => {
if (document.pointerLockElement !== canvas) return;
if (e.buttons === 1) {
this.ctx.lineTo(e.clientX, e.clientY);
this.ctx.stroke();
}
});
canvas.addEventListener('mousedown', (e) => {
if (document.pointerLockElement === canvas) {
this.ctx.beginPath();
this.ctx.moveTo(e.clientX, e.clientY);
}
});
}
}
五、权限与安全
5.1 用户交互要求
Pointer Lock 需要用户交互(点击)才能激活,这是浏览器的安全要求:
// ❌ 直接调用会失败
canvas.requestPointerLock(); // 错误:需要在用户手势中调用
// ✅ 在点击事件中调用
canvas.addEventListener('click', async () => {
await canvas.requestPointerLock();
});
5.2 退出锁定
用户可以通过 ESC 键退出 Pointer Lock:
document.addEventListener('pointerlockchange', () => {
if (document.pointerLockElement === null) {
// 用户主动退出或按下 ESC
console.log('Pointer lock released');
}
});
5.3 unadjustedMovement 选项
// 获取"原始"鼠标移动,不受 OS 加速影响
canvas.addEventListener('click', async () => {
await canvas.requestPointerLock({
unadjustedMovement: true
});
});
六、面试高频问题
Q: Pointer Lock 和普通鼠标事件有什么区别?
回答要点:普通鼠标事件基于绝对位置,每次事件报告相对于视口的位置;Pointer Lock 隐藏光标,mousemove 事件的 movementX/Y 报告自上次事件以来的累积增量,适合需要无限移动的场景如 3D 游戏。
Q: 什么场景适合使用 Pointer Lock?
回答要点:3D 游戏(FPS 视角控制)、3D 场景查看器、绘图应用、需要精确鼠标控制的任何场景。Pointer Lock 解决了普通鼠标事件在边界处停止的问题。
Q: 为什么 Pointer Lock 需要用户点击才能激活?
回答要点:浏览器安全策略要求——不允许网页在未经用户同意的情况下隐藏光标并拦截鼠标输入,这会使用户无法退出或与其他应用交互。