ポインターロック API
ポインターロック API (以前は マウスロック API と呼ばれていました)は、ビューポート内のマウスカーソルの絶対位置だけでなく、時間の経過に伴うマウスの動き(すなわち、デルタ)に基づく入力方法を提供します。これにより、マウスの生の移動量を知ることができ、マウスイベントのターゲットをひとつの要素にロックでき、マウスが一方向へどれだけ移動できるかの制限を除去でき、視野からカーソルを取り除くことができます。これらは、本人視点の 3D ゲームなどで理想的です。
さらにこの API は、動きのコントロールやオブジェクトの回転、エントリーの変更にかなりのマウス操作が必要になるアプリケーションで役立ちます。例えばなんらかのボタンをクリックすることなく、マウスを動かすだけで視野角を制御できます。ボタンは他の操作のために使用できます。また、地図や衛星画像を見るアプリでも役に立ちます。
ポインターロックでは、カーソルがブラウザーやスクリーンの境界を通り過ぎるときでもマウスイベントにアクセスできます。例えばユーザーは限りなくマウスを動かすことで、3D モデルの回転や操作を続けることができます。ポインターロックがなければ、ポインターがブラウザーまたはスクリーンの端に達したときに回転や操作が止まります。ゲームのプレイヤーはマウスカーソルがゲームのプレイ領域から外れて、別のアプリケーションを意図せずクリックしてゲームからマウスのフォーカスが外れてしまうことを心配せずに、熱中してボタンをクリックしたり、マウスカーソルをあちこちに動かしたりすることができるようになります。
基本概念
ポインターロックは マウスキャプチャ と関係があります。マウスキャプチャはマウスのドラッグ中にターゲットの要素へ継続的にイベントを提供しますが、マウスのボタンを離すとイベントが停止します。ポインターロックとマウスキャプチャの違いは以下のとおりです。
- ポインターロックは永続的です。明示的に API が呼び出されるかユーザーが特定の解放ジェスチャを行うまで、マウスを解放しません。
- ポインターロックはブラウザーまたはスクリーンの境界に制限されません。
- ポインターロックはマウスボタンの状態に関係なく、イベントが発生し続けます。
- ポインターロックはカーソルを隠します。
メソッド/プロパティの概要
ここでは、ポインターロック仕様に関係するプロパティやメソッドを簡単に説明します。
requestPointerLock()
ポインターロック API は 全画面 API と同様に、 DOM 要素に新たなメソッド requestPointerLock()
を追加することで拡張しています。接頭辞が最近削除されましたので、例えば canvas
要素で ポインターロックを要求したい場合は、現在は以下のように宣言します。
canvas.requestPointerLock =
canvas.requestPointerLock || canvas.mozRequestPointerLock;
canvas.requestPointerLock();
メモ:
ユーザーが既定のロック解除ジェスチャでポインターロックを解除した場合、またはこの文書に対して以前にポインターロックが入力されていない場合、エンゲージメントジェスチャの結果として生成されるイベントを requestPointerLock
が成功する前に、文書内で受信する必要があります。(https://w3c.github.io/pointerlock/#extensions-to-the-element-interface より)
pointerLockElement と exitPointerLock()
ポインターロック API は Document
インターフェイスも拡張しており、新たなプロパティやメソッドを追加しています。新たなプロパティ pointerLockElement
は、現在ロックしている要素がある場合に、その要素へアクセスするために使用します。また、 Document
新たなメソッドである exitPointerLock()
は、名前が示唆するとおりポインターロックを終えるために使用します。
pointerLockElement
プロパティは要素が現在ポインターがロック状態であるかを判断する(例えば論理型でチェックを行う)ため、またはロックされた要素があればその要素への参照を得るために有用です。
pointerLockElement
の使用例を示します。
if (
document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas
) {
console.log("The pointer lock status is now locked");
} else {
console.log("The pointer lock status is now unlocked");
}
Document.exitPointerLock()
メソッドはポインターロックを終えるために使用され、requestPointerLock()
と同様に pointerlockchange
イベントや pointerlockerror
イベントを用いて非同期的に動作します。使用例は以下のとおりです。
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("The pointer lock status is now locked");
// 応答として役に立つ処理
} else {
console.log("The pointer lock status is now unlocked");
// 応答として役に立つ処理
}
}
pointerlockerror イベント
requestPointerLock()
または exitPointerLock()
の呼び出しによりエラーが発生したときは、pointerlockerror
イベントが document
に発生します。これはシンプルなイベントであり、付加的なデータは含まれません。
document.addEventListener("pointerlockerror", lockError, false);
document.addEventListener("mozpointerlockerror", lockError, false);
function lockError(e) {
alert("Pointer lock failed");
}
メモ:
Firefox 50 まで、上記のイベントは moz
接頭辞を付加していました。
Mouse イベントの拡張
ポインターロック API は通常の MouseEvent
インターフェイスを、movement 属性で拡張します。新たな 2 つの属性 movementX
および movementY
が、マウスポインターの位置の変化を提供します。引数の値は MouseEvent
のプロパティである screenX
および screenY
の値同士の差と同じであり、それらのプロパティは 2 つ続いて発生する mousemove
イベント eNow
および ePrevious
に保存されます。言い換えると、ポインターロックのパラメーター movementX
は、eNow.screenX - ePrevious.screenX
になります。
ロックされた状態
ロックが解除された状態
シンプルな例のウォークスルー
ポインターロックの使用方法やシンプルな制御システムの設定方法を示すため、シンプルな ポインターロックのデモ を作成しました (ソースコードを確認する)。このデモでは、JavaScript を使用して <canvas>
要素上にボールを描画します。canvas をクリックすると ポインターロックがマウスポインターを取り除いて、マウスを使用してボールを直接動かすことができます。このデモの仕組みを見ていきましょう。
canvas 内の、x および y の初期位置を設定します。
const x = 50;
const y = 50;
現在は ポインターロックのメソッドに接頭辞がついていますので、ブラウザー実装ごとに処理を分けています。
canvas.requestPointerLock =
canvas.requestPointerLock || canvas.mozRequestPointerLock;
document.exitPointerLock =
document.exitPointerLock || document.mozExitPointerLock;
キャンバスがクリックされたときに、キャンバスで requestPointerLock()
メソッドを実行するイベントリスナーを設定します。これは、ポインターロックを開始します。
canvas.onclick = () => {
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("The pointer lock status is now locked");
document.addEventListener("mousemove", updatePosition, false);
} else {
console.log("The pointer lock status is now unlocked");
document.removeEventListener("mousemove", updatePosition, false);
}
}
updatePosition() 関数が、キャンバス内のボールの位置 (x
および y
) を更新します。また、ボールがキャンバスの端からはみ出すかをチェックする if()
文が含まれています。ボールがはみ出す場合は、反対側の端にボールを描画します。また、requestAnimationFrame()
がすでに呼び出されたかを確認しており、呼び出された場合は必要に応じて再び呼び出して、キャンバスのシーンを更新するために canvasDraw()
関数を呼び出します。さらに、参照用に X および Y の位置を表示するための tracker も設定します。
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}`;
if (!animation) {
animation = requestAnimationFrame(() => {
animation = null;
canvasDraw();
});
}
}
canvasDraw()
関数は、現在の x
および y
の位置にボールを描画します。
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 は 1 つだけです。 iframe をひとつロックすると、別の iframe をロックしてターゲットを切り替えようとすることはできません。ポインターロックはエラーになります。この制限を避けるため、始めにロックされた iframe のロックを解除してから別の iframe をロックしてください。
iframe の既定の動作では、「サンドボックス化された」 iframe が ポインターロックをブロックします。この制限を回避するには、 <iframe sandbox="allow-pointer-lock">
を使用してください。
仕様書
Specification |
---|
Pointer Lock 2.0 |
ブラウザーの互換性
api.Document.exitPointerLock
BCD tables only load in the browser
api.Element.requestPointerLock
BCD tables only load in the browser