雑記帳

整理しない情報集

更新情報

setPointerCaptureでドラッグ座標をウィンドウ外まで捕捉する

公開日:

カテゴリ: JavaScript

JavaScriptでドラッグによる移動やリサイズのイベントを、ウィンドウの外まで補足する方法を紹介します。そこそこ前からあったAPIのようですが、筆者は割と最近知りました。

この機能が無いと困るというケースは少なそうですが、あるとUX的には非常に有効そうです。

方法

方法は至ってシンプルで、ドラッグ開始時に対象要素のsetPointerCapture()にポインタIDを引数にして実行するだけです。ポインタIDは、ポインタイベントを発生させたときのイベントオブジェクトから取得できます。

setPointerCapture()を実行すると、その要素に設定されているPointerEventがその要素を超えて(ブラウザのウィンドウも超えて)発生するようになります。また、イベントターゲット(e.target)が実行した要素に固定されます。したがって、pointeroverpointerenterpointerleavepointeroutイベントは発生しなくなります。

setPointerCapture()pointerupもしくはpointercancelイベントが発生するか、releasePointerCapture()を実行すると解除されます。また(少なくともChromeでは)、マウスボタンが押下状態でないとsetPointerCapture()は無視されるようです(pointermoveイベントでsetPointerCapture()を実行すると、マウスボタンが押下状態の場合のみ機能します)。

document.getElementById("test").addEventListener("pointerdown", (e) => {
	setPointerCapture(e.pointerId);
});

使用例

サンプルの表示にはiframeを使用して擬似的に再現しています。iframe(背景が白のエリア)としては、外側(親ページ)が通常ページのウィンドウ外と大体同じです。

要素の移動

<div data-movable></div>
const usePointerCapture = (e) => {
	const elem = e.currentTarget;
	elem.setPointerCapture(e.pointerId);
	const {offsetX, offsetY} = e;
	const move = (e) => {
		elem.style.left = (e.x - offsetX) + "px";
		elem.style.top = (e.y - offsetY) + "px";
	};
	elem.addEventListener("pointermove", move);
	elem.addEventListener("pointerup", () => {
		elem.removeEventListener("pointermove", move);
	});
};
for (const elem of [...document.querySelectorAll("[data-movable]")]) {
	elem.addEventListener("pointerdown", usePointerCapture);
}

setPointerCaptureを使わない場合

3行目のelem.setPointerCapture(e.pointerId);の有無以外は同一のコードです。

イベントを設定する要素とカーソルに合わせて移動させる要素が同一であるため、勢いをつけてドラッグする(次のpointermoveイベントが発火する前にカーソルがドラッグしている要素の外側に移動する)と、要素がカーソルについて来れなくなります。またiframeの外側にドラッグしてもついて来れなくなります。さらにこれらの状態になると、次にその要素の上にカーソルが乗った際に、マウスボタンの押下状態にかかわらず要素がカーソルについてきます。

前者は要素を動かす範囲の親要素と同等以上(documentなど)にイベントを設定すれば一応回避できますが、多数のpointerdownを扱うページの場合は、なるべくpointerdownと同じ要素にイベントを設定したほうが扱いやすいですよね。後者はsetPointerCapture()以外に解決策は無さそうです。最後の点もsetPointerCapture()を使うことでpointerupをどこでも拾うことができるので解決できます。

その他

  • pointerIdPointerEvent以外から取得する方法は無さそうです

参考

Element: setPointerCapture() メソッド - Web API | MDN

カテゴリ: JavaScript