Pointer Lock API
Pointer Lock API(以前稱為 Mouse Lock API)提供基於滑鼠隨時間移動(即差值)的輸入方法,而不僅僅是滑鼠游標在視口中的絕對位置。它可以訪問原始滑鼠移動,將滑鼠事件的目標鎖定到單個元素,消除滑鼠單方向移動距離的限制,並隱藏游標。它非常適合第一人稱 3D 遊戲等應用。
更重要的是,該 API 對於任何需要大量滑鼠輸入來控制移動、旋轉物件和更改輸入的應用程式都很有用,例如,允許使用者透過移動滑鼠進行控制,而無需單擊任何按鈕。這樣,按鈕就可以用於其他操作。其他示例包括地圖或衛星影像檢視應用程式。
Pointer lock 允許您即使在游標超出瀏覽器或螢幕邊界時也能訪問滑鼠事件。例如,您的使用者可以透過持續移動滑鼠來旋轉或操作 3D 模型。如果沒有 Pointer lock,旋轉或操作會在指標到達瀏覽器或螢幕邊緣時停止。遊戲玩家現在可以單擊按鈕,來回滑動滑鼠游標,而無需擔心離開遊戲區域並意外單擊另一個應用程式,從而將滑鼠焦點從遊戲中移開。
基本概念
Pointer lock 與 pointer capture 相關。Pointer capture 在拖動滑鼠時持續將事件傳遞給目標元素,但在釋放滑鼠按鈕時停止。Pointer lock 與 pointer capture 的區別如下:
- 它具有永續性:Pointer lock 在進行明確的 API 呼叫或使用者使用特定的釋放手勢之前,不會釋放滑鼠。
- 它不受瀏覽器或螢幕邊界的限制。
- 無論滑鼠按鈕狀態如何,它都會繼續傳送事件。
- 它會隱藏游標。
方法/屬性概述
本節簡要介紹與 pointer lock 規範相關的每個屬性和方法。
requestPointerLock()
Pointer lock API 類似於 Fullscreen API,透過新增一個新方法 requestPointerLock() 來擴充套件 DOM 元素。以下示例在 <canvas> 元素上請求 pointer lock。
canvas.addEventListener("click", async () => {
await canvas.requestPointerLock();
});
注意: 如果使用者透過 預設解鎖手勢 退出了 pointer lock,或者該文件之前沒有進入 pointer lock,則在 requestPointerLock 成功之前,文件必須收到由 參與手勢 生成的事件。(來自 https://w3c.github.io/pointerlock/#extensions-to-the-element-interface)
作業系統預設啟用滑鼠加速,這在您有時需要緩慢精確移動(例如使用圖形軟體包時),但也想透過更快的滑鼠移動來移動大距離(例如滾動和選擇多個檔案時)時很有用。然而,對於某些第一人稱視角遊戲,原始滑鼠輸入資料對於控制相機旋轉更為可取——即,相同距離的移動,無論快慢,都會產生相同的旋轉。根據專業遊戲玩家的說法,這可以帶來更好的遊戲體驗和更高的準確性。
要停用作業系統級別的滑鼠加速並訪問原始滑鼠輸入,您可以將 unadjustedMovement 設定為 true。
canvas.addEventListener("click", async () => {
await canvas.requestPointerLock({
unadjustedMovement: true,
});
});
處理 requestPointerLock() 的 Promise 和非 Promise 版本
上述程式碼片段在不支援 requestPointerLock() 的 Promise 版本或 unadjustedMovement 選項的瀏覽器中仍然有效——await 運算子可以放在不返回 Promise 的函式前面,而在不支援的瀏覽器中,options 物件將被忽略。
然而,這可能會令人困惑,並且存在其他潛在的副作用(例如,在不支援的瀏覽器中嘗試使用 requestPointerLock().then() 會丟擲錯誤),因此您可能希望使用類似以下的程式碼來顯式處理:
function requestPointerLockWithUnadjustedMovement() {
const promise = myTargetElement.requestPointerLock({
unadjustedMovement: true,
});
if (!promise) {
console.log("disabling mouse acceleration is not supported");
return;
}
return promise
.then(() => console.log("pointer is locked"))
.catch((error) => {
if (error.name === "NotSupportedError") {
// Some platforms may not support unadjusted movement.
// You can request again a regular pointer lock.
return myTargetElement.requestPointerLock();
}
});
}
pointerLockElement 和 exitPointerLock()
Pointer lock API 還擴充套件了 Document 介面,添加了一個新屬性和一個新方法:
pointerLockElement用於訪問當前鎖定的元素(如果有)。exitPointerLock()用於退出 pointer lock。
pointerLockElement 屬性對於確定是否有元素當前被 pointer lock(例如,用於布林檢查)以及獲取對被鎖定元素(如果有)的引用非常有用。
這是使用 pointerLockElement 的示例:
if (document.pointerLockElement === canvas) {
console.log("The pointer lock status is now locked");
} else {
console.log("The pointer lock status is now unlocked");
}
Document.exitPointerLock() 方法用於退出 pointer lock,並且與 requestPointerLock 一樣,它使用 pointerlockchange 和 pointerlockerror 事件非同步工作,您稍後會看到更多關於這些事件的內容。
document.exitPointerLock();
pointerlockchange 事件
當 Pointer lock 狀態發生變化時——例如,當呼叫 requestPointerLock() 或 exitPointerLock()、使用者按下 ESC 鍵等——pointerlockchange 事件會被分派到 document。這是一個不包含額外資料的簡單事件。
document.addEventListener("pointerlockchange", lockChangeAlert);
function lockChangeAlert() {
if (document.pointerLockElement === canvas) {
console.log("The pointer lock status is now locked");
// Do something useful in response
} else {
console.log("The pointer lock status is now unlocked");
// Do something useful in response
}
}
pointerlockerror 事件
當呼叫 requestPointerLock() 或 exitPointerLock() 導致錯誤時,pointerlockerror 事件會被分派到 document。這是一個不包含額外資料的簡單事件。
document.addEventListener("pointerlockerror", lockError);
function lockError(e) {
alert("Pointer lock failed");
}
滑鼠事件的擴充套件
Pointer lock API 使用 movement 屬性擴充套件了標準的 MouseEvent 介面。滑鼠事件的兩個新屬性——movementX 和 movementY——提供了滑鼠位置的變化。這些引數的值與 MouseEvent 屬性 screenX 和 screenY 的值之間的差異相同,這些差異儲存在兩個連續的 mousemove 事件 eNow 和 ePrevious 中。換句話說,Pointer lock 引數 movementX = eNow.screenX - ePrevious.screenX。
鎖定狀態
當 Pointer lock 啟用時,標準的 MouseEvent 屬性 clientX, clientY, screenX, 和 screenY 會保持不變,彷彿滑鼠沒有移動一樣。movementX 和 movementY 屬性會繼續提供滑鼠位置的變化。如果滑鼠在單個方向上持續移動,movementX 和 movementY 的值沒有限制。滑鼠游標的概念不存在,游標也不能移出視窗或被螢幕邊緣限制。
解鎖狀態
無論滑鼠鎖定狀態如何,movementX 和 movementY 引數都有效,並且為了方便起見,即使在解鎖狀態下也可以使用。
當滑鼠解鎖時,系統游標可以退出和重新進入瀏覽器視窗。如果發生這種情況,movementX 和 movementY 可能會被設定為零。
簡單示例演示
我們編寫了一個 pointer lock 演示(檢視原始碼),向您展示如何使用它來設定一個簡單的控制系統。此演示使用 JavaScript 在 <canvas> 元素之上繪製一個球。當您單擊畫布時,pointer lock 會被用於移除滑鼠指標,並允許您直接透過滑鼠移動球。讓我們看看它是如何工作的。
我們在畫布上設定初始的 x 和 y 位置。
let x = 50;
let y = 50;
接下來,我們設定一個事件監聽器,在畫布被單擊時執行 requestPointerLock() 方法,從而啟動 pointer lock。document.pointerLockElement 檢查是為了檢視是否已經有一個活動的 pointer lock——如果我們已經在畫布內單擊並獲得了 pointer lock,我們不想每次都再次呼叫 requestPointerLock()。
canvas.addEventListener("click", async () => {
if (!document.pointerLockElement) {
await canvas.requestPointerLock({
unadjustedMovement: true,
});
}
});
注意: 上面的程式碼片段在不支援 requestPointerLock() Promise 版本的瀏覽器中也能正常工作。有關說明,請參閱 Handling promise and non-promise versions of requestPointerLock()。
現在是專用的 pointer lock 事件監聽器:pointerlockchange。當它發生時,我們會執行一個名為 lockChangeAlert() 的函式來處理變化。
document.addEventListener("pointerlockchange", lockChangeAlert);
此函式檢查 pointerLockElement 屬性,看它是否是我們的畫布。如果是,它會附加一個事件監聽器來使用 updatePosition() 函式處理滑鼠移動。如果不是,它會再次移除事件監聽器。
function lockChangeAlert() {
if (document.pointerLockElement === canvas) {
console.log("The pointer lock status is now locked");
document.addEventListener("mousemove", updatePosition);
} else {
console.log("The pointer lock status is now unlocked");
document.removeEventListener("mousemove", updatePosition);
}
}
updatePosition() 函式更新畫布上的球的位置(x 和 y),還包括 if () 語句來檢查球是否已超出畫布邊緣。如果是,它會讓球繞到另一側邊緣。它還包括一個檢查,看是否之前呼叫了 requestAnimationFrame(),如果呼叫了,則會再次按需呼叫它,並呼叫 canvasDraw() 函式來更新畫布場景。還會設定一個跟蹤器,將 X 和 Y 值寫入螢幕,以供參考。
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 position: ${x}, Y position: ${y}`;
animation ??= requestAnimationFrame(() => {
animation = null;
canvasDraw();
});
}
canvasDraw() 函式在當前的 x 和 y 位置繪製球。
function canvasDraw() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, y, RADIUS, 0, degToRad(360), true);
ctx.fill();
}
IFrame 限制
Pointer lock 一次只能鎖定一個 <iframe>。如果您鎖定了一個 <iframe>,則無法鎖定另一個並將其目標轉移到它;pointer lock 會出錯。要避免此限制,請先解鎖已鎖定的 <iframe>,然後再鎖定另一個。
雖然 <iframe> 預設可用,但“沙盒化”的 <iframe> 會阻止 Pointer lock。要避免此限制,請使用 <iframe sandbox="allow-pointer-lock">。
規範
| 規範 |
|---|
| 指標鎖定 2.0 |
瀏覽器相容性
api.Document.exitPointerLock
載入中…
api.Element.requestPointerLock
載入中…