포인터 잠금 2.0

W3C 워킹 드래프트

이 문서에 대한 자세한 정보
이 버전:
https://www.w3.org/TR/2024/WD-pointerlock-2-20240617/
최신 공개 버전:
https://www.w3.org/TR/pointerlock-2/
최신 에디터 드래프트:
https://w3c.github.io/pointerlock/
역사:
https://www.w3.org/standards/history/pointerlock-2/
커밋 히스토리
테스트 스위트:
https://github.com/web-platform-tests/wpt/tree/master/pointerlock
에디터:
Mustaq Ahmed (Google)
Vincent Scheib (Google)
이전 에디터:
Navid Zolghadr (Google)
피드백:
GitHub w3c/pointerlock (풀 리퀘스트, 새 이슈, 오픈 이슈)
브라우저 지원:
caniuse.com

개요

이 명세서는 스크립트로 원시 마우스 움직임 데이터를 접근할 수 있는 API를 정의하며, 마우스 이벤트의 대상을 단일 요소로 고정하고 커서를 화면에서 제거합니다. 이러한 입력 방식은 특정 종류의 애플리케이션, 특히 1인칭 시점의 3D 애플리케이션과 3D 모델링 소프트웨어에 필수적입니다.

이 문서의 상태

이 섹션은 이 문서가 발행된 시점의 상태를 설명합니다. 현재 W3C 출판물 목록과 이 기술 보고서의 최신 개정판은 W3C 기술 보고서 인덱스 https://www.w3.org/TR/에서 확인할 수 있습니다.

이 문서의 변경 이력은 https://github.com/w3c/pointerlock/commits/gh-pages/index.html에서 확인할 수 있습니다.

2016년 10월 27일 W3C 권고안 이후의 변경 요약:

이 문서는 웹 애플리케이션 워킹 그룹에서 권고안 경로를 사용하여 워킹 드래프트로 발행되었습니다.

워킹 드래프트로서의 발행은 W3C 및 그 회원사의 지지를 의미하지 않습니다.

이 문서는 초안이며, 언제든지 다른 문서로 업데이트, 대체, 폐기될 수 있습니다. 진행 중인 작업 외에는 이 문서를 인용하는 것은 부적절합니다.

이 문서는 W3C 특허 정책에 따라 운영되는 그룹에서 작성되었습니다. W3C관련 특허 공개의 공개 목록을 관리합니다. 해당 페이지에는 특허 공개 방법에 대한 안내도 포함되어 있습니다. 특정 특허가 필수 권리 주장을 포함한다고 판단되는 경우, W3C 특허 정책 6절에 따라 정보를 공개해야 합니다.

이 문서는 2023년 11월 3일 W3C 프로세스 문서에 따라 관리됩니다.

1. 소개

이 절은 규범적이지 않습니다.

포인터 잠금 API는 애플리케이션이 마우스 커서의 위치만 읽는 것에 제한되지 않고, 마우스 움직임 자체를 입력 방식으로 직접 해석할 수 있게 해줍니다. 대표적인 예는 3차원 그래픽 애플리케이션(게임 등)에서의 1인칭 이동 컨트롤입니다: 마우스의 움직임이 플레이어의 카메라 회전/방향을 제어하며, 마우스 커서는 표시되지 않고, 움직임이 기존의 경계(예: 사용자 에이전트 창, 전체 화면 등)에 제한되지 않아 마우스 움직임을 어느 방향으로든 무한히 추적할 수 있습니다.

포인터 잠금은 Mouse Capture [MDN-SETCAPTURE]와 관련이 있습니다(마우스 캡처는 명세화되어 있지 않음: 버그 14600). 캡처는 마우스가 드래그되는 동안 타겟 요소에 이벤트 전달을 지속하지만, 마우스 버튼이 놓이면 중단됩니다. 포인터 잠금은 지속적이며, 화면 경계에 제한되지 않고, 마우스 버튼 상태와 관계없이 이벤트를 보내며, 커서를 숨기고, API 호출이나 사용자의 특정 기본 잠금 해제 제스처가 있을 때까지 해제되지 않습니다.

포인터 잠금은 단일 리소스를 캡처하고 이를 단일 요소에 연결하는 기능을 다룹니다. 이는 Fullscreen API [FULLSCREEN]와 유사하며, 해당 API는 단일 요소를 전체 화면으로 승격합니다. 포인터 잠금 API는 리소스 캡처, 상태 변화, 해제 API를 Fullscreen API와 최대한 유사하게 설계하였습니다.

포인터 잠금 상호작용 모드는 이전에는 마우스 잠금으로 불렸습니다. 마우스 이외에도 다양한 컨트롤러 타입이 화면상의 포인팅 커서를 조작할 수 있으므로, 모두 영향을 받는다는 의미를 담아 명칭이 변경되었습니다.

2. 포인터 잠금과 인터페이스

2.1 포인터 잠금 상태 정의

포인터 잠금 상태란, 하나의 DOM 요소(포인터-잠금 대상이라 부름)가 모든 마우스 이벤트를 받고 커서가 숨겨지는 상태를 의미합니다.

포인터 잠금 상태에 들어가면 사용자 에이전트포인터-잠금 대상, 포인터-잠금 옵션(PointerLockOptions), 그리고 커서 위치(포인터 잠금 상태 진입 시 시스템 마우스 커서의 위치, screenX, screenY로 보고됨)을 갖게 됩니다. 포인터-잠금 대상은 모든 사용자 생성 MouseEvent 이벤트(즉, mousemove, mousedown, mouseup, click, dblclick, auxclick, wheel [ui-events])를 모두 받습니다. 포인터 잠금 상태에서는 다른 요소가 이러한 이벤트를 받지 않습니다. 마우스 커서 개념이 필요한 이벤트(예: mouseenter, mouseleave, mouseover, mouseout, drag, drop)는 디스패치되지 않습니다.

Issue 97: 2.1절에는 잠금이 PointerEvents에 미치는 영향도 언급해야 함

이 내용은 이 스레드#49에서 제기되었습니다:

이벤트 목록을 예시만 들지 말고 규범적으로 명시할 수 있나요?

현재 텍스트도 괜찮다고 생각하지만, 여기서의 불명확함은 전체적으로 마우스 이벤트 명세가 불명확한 점의 일부일 뿐입니다. 일부 진전은 w3c/uievents#200에서 이루어지고 있으며, 해당 논의가 결론나면 이 문단을 UI 이벤트 명세의 처리 모델 변경으로 이관할 수 있습니다.

하지만 그 전까지는 호환성을 위해 이벤트 목록을 명확하게 규정하는 것이 도움이 될 것입니다.

예를 들어, pointer 이벤트가 이 변경의 영향을 받는지 명확하지 않습니다.

포인터 잠금 상태에서 포인터-잠금 옵션unadjustedMovement 멤버가 true이면, 이벤트 좌표가 플랫폼의 마우스 가속 등 기본 동작에 영향을 받지 않습니다. 즉, 사용자 에이전트는 플랫폼에서 제공하는 API를 사용하여 원시 이벤트를 보장합니다. PointerLockOptionsunadjustedMovementfalse이면, 사용자 에이전트는 플랫폼의 기본 마우스 가속 동작을 따릅니다.

포인터 잠금 상태에서는 시스템 마우스 커서가 숨겨지고, 마우스 이동이나 버튼 클릭과 상관없이 윈도우 포커스가 잃지 않도록 합니다. 이는 OS API를 직접 또는 간접적으로 활용하여 달성됩니다.

참고

애플리케이션 스크립트로 생성된 합성 마우스 이벤트는 잠금 상태와 관계없이 동일하게 동작합니다.

2.2 PointerLockOptions 딕셔너리

WebIDLdictionary PointerLockOptions {
  boolean unadjustedMovement = false;
};
PointerLockOptions 딕셔너리

잠금 모드에서 포인터 동작을 커스터마이즈할 수 있는 옵션 딕셔너리입니다.

unadjustedMovement 멤버

이 값이 true로 설정되면, 포인터 움직임은 플랫폼의 마우스 가속 등 수정 동작의 영향을 받지 않습니다.

2.3 pointerlockchangepointerlockerror 이벤트

포인터 잠금 상태 변화 또는 상태 변경 오류를 알리기 위해 두 가지 이벤트가 사용됩니다. 각각 pointerlockchangepointerlockerror입니다. 자세한 내용은 3. Element 인터페이스의 확장 절의 알고리즘을 참조하십시오.

참고

화면 확대 소프트웨어는 화면의 콘텐츠를 확대합니다. 확대 포커스를 이동시키기 위해 마우스를 사용하지만, 포인터 잠금이 시작되면 키보드를 통해 확대 포커스 이동을 해야 합니다. pointerlockchange 이벤트가 발생하면, 웹 브라우저는 해당 이벤트가 화면 확대 등 접근성 기술에 전달되도록 해야 합니다.

2.4 포인터 잠금 해제

element에 대해 포인터 잠금 해제 과정은 다음과 같습니다:

  1. 시스템 마우스 커서를 다시 표시하고, 커서 위치에 배치해야 합니다.
  2. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 이벤트를 발생시키고 pointerlockchange라는 이름으로 해당 element노드 문서에 전달합니다.
  3. 포인터 잠금 상태를 종료하고, 포인터-잠금 대상, 포인터-잠금 옵션, 커서 위치를 null로 설정합니다.

3. Element 인터페이스의 확장

Element 인터페이스는 포인터를 잠그는 기능을 요청할 수 있도록 확장되었습니다.

WebIDLpartial interface Element {
  Promise<undefined> requestPointerLock(optional PointerLockOptions options = {});
};
const lock_element = document.getElementById("lock_element");
const lock_button = document.getElementById("lock");
lock_button.addEventListener("click", async (event) => {
    try {
        await lock_element.requestPointerLock({ unadjustedMovement: true });
        console.log("성공적으로 잠금됨!");
    } catch (e) {
        console.log("잠금 실패, 에러: ", e);
    }
});
requestPointerLock(PointerLockOptions options) 메서드

lock requests queue라는 이름의 병렬 큐를 사용하여 모든 요청을 큐에 저장합니다. requestPointerLock() 호출 시 다음 단계를 수행합니다:

  1. promise새로운 promise로 생성합니다.
  2. 어떤 window포커스 상태일 때, thisshadow-including root활성 문서탐색 컨텍스트(또는 조상 탐색 컨텍스트)가 포커스되어 있지 않으면:
    1. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
      1. pointerlockerror 이벤트를 this노드 문서에 발생시킵니다.
      2. promise를 Reject시키며 "WrongDocumentError" DOMException을 전달합니다.
    2. promise를 반환합니다.
    Issue 93: 가시성 상태 확인

    명세는 숨겨진 문서가 포인터 잠금을 요청하지 못하도록 해야 합니다.

    또한 문서가 숨겨지면 포인터 잠금을 해제해야 합니다.

  3. 관련 전역 객체에 일시적 활성화가 없고 DocumentexitPointerLock() 호출로 성공적으로 포인터 잠금을 해제한 적이 없다면:
    1. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
      1. pointerlockerror 이벤트를 this노드 문서에 발생시킵니다.
      2. promise를 Reject시키며 "NotAllowedError" DOMException을 전달합니다.
    2. promise를 반환합니다.
    참고
  4. this노드 문서활성 샌드박싱 플래그 세트샌드박스 포인터 잠금 탐색 컨텍스트 플래그가 설정되어 있다면:
    1. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
      1. pointerlockerror 이벤트를 this노드 문서에 발생시킵니다.
      2. promise를 Reject시키며 "SecurityError" DOMException을 전달합니다.
    2. promise를 반환합니다.
  5. options["unadjustedMovement"] 값이 true이고, 플랫폼에서 unadjustedMovement를 지원하지 않으면:
    1. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
      1. pointerlockerror 이벤트를 this노드 문서에 발생시킵니다.
      2. promise를 Reject시키며 "NotSupportedError" DOMException을 전달합니다.
    2. promise를 반환합니다.
    Issue 90: `unadjustedMovement` 지원 여부를 나타내는 static 필드 제안

    PR #49에서 PointerLockOptions가 도입되었습니다. 하지만 PointerLockOptionsunadjustedMovement가 모든 플랫폼에서 지원되지 않을 수 있습니다. 직접 사용하기 전에 지원 여부를 확인할 수 있도록 static 필드가 유용할 수 있습니다.

  6. lock requests queue에 다음 단계를 큐에 추가합니다:
    1. 사용자 에이전트의 pointer-lock targetshadow-including rootthisshadow-including root와 다르다면:
      1. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
        1. pointerlockerror 이벤트를 this노드 문서에 발생시킵니다.
        2. promise를 Reject시키며 "InvalidStateError" DOMException을 전달합니다.
    2. 사용자 에이전트의 pointer-lock targetshadow-including rootthisshadow-including root와 같고, options가 현재 pointer-lock options와 동일하다면:
      1. pointer-lock targetthis로 설정합니다.
      2. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
        1. pointerlockchange 이벤트를 this노드 문서에 발생시킵니다.
        2. promise를 Resolve합니다.
    3. pointer-lock target이 null이거나, options가 현재 pointer-lock options와 동일하지 않으면, lock 요청을 처리하고 완료 시 응답을 처리:
      1. lock 요청이 실패한 경우:
        1. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
          1. pointerlockerror 이벤트를 this노드 문서에 발생시킵니다.
          2. promise를 Reject시키며 "NotSupportedError" DOMException을 전달합니다.
        Issue 91: 후속 requestPointerLock이 거부될 때, 이미 잠금된 대상이 잠금 상태를 해제해야 하는가?

        PR #49의 requestPointerLock 알고리즘에서, 후속 요청이 어떤 이유로든 실패할 때, 이미 잠금된 대상이 잠금 상태를 해제해야 하는지 설명이 없습니다.

      2. lock 요청이 성공한 경우:
        1. pointer lock 상태로 진입하며, 다음을 수행:
          1. 사용자 에이전트의 pointer-lock targetthis로 설정합니다.
          2. 사용자 에이전트의 커서 위치를 현재 시스템 마우스 위치로 설정합니다.
          3. pointer-lock options를 options로 설정합니다.
        2. 요소 작업을 큐에 추가하여, 사용자 상호작용 작업 소스에서 this를 대상으로 다음 단계를 수행:
          1. pointerlockchange 이벤트를 this노드 문서에 발생시킵니다.
          2. promise를 Resolve합니다.
    Issue 96: lock requests queue 처리 미완성

    PR #49의 requestPointerLock 알고리즘에서, lock requests queue의 항목 처리에 대한 설명이 없습니다. 명시적 설명이 없으면 모든 항목이 동기적으로 큐에서 제거된다는 잘못된(?) 가정이 남을 수 있습니다.

  7. promise를 반환합니다.
참고

4. Document 인터페이스의 확장

WebIDLpartial interface Document {
  attribute EventHandler onpointerlockchange;
  attribute EventHandler onpointerlockerror;
  undefined exitPointerLock();
};
onpointerlockchange 속성

이벤트 핸들러 IDL 속성으로, pointerlockchange 이벤트를 처리합니다.

onpointerlockerror 속성

이벤트 핸들러 IDL 속성으로, pointerlockerror 이벤트를 처리합니다.

exitPointerLock() 메서드
  1. 사용자 에이전트의 pointer-lock target이 null이면 반환합니다.
  2. pointer-lock targetshadow-including rootthis노드 문서와 다르면 반환합니다.
  3. 사용자 에이전트의 pointer-lock target을 사용하여 포인터 잠금 해제를 수행합니다.

5. DocumentOrShadowRoot 믹스인의 확장

WebIDLpartial interface mixin DocumentOrShadowRoot {
  readonly attribute Element? pointerLockElement;
};
pointerLockElement

포인터가 잠긴 동안, 해당 요소를 재타겟팅한 결과를 반환합니다. 이 재타겟팅된 요소가 마우스 이벤트의 대상이 되며, this 요소와 같은 트리에 속해 있다면 그 결과 요소를, 아니라면 null을 반환합니다.

lock이 대기 중이거나 포인터가 잠금 해제 상태라면 null을 반환합니다.

<body>
  <div id="host1">
    <shadow-root id="root1">
      <canvas id="canvas1"></canvas>
    </shadow-root>
  </div>
  <div id="host2">
    <shadow-root id="root2">
      <canvas id="canvas2"></canvas>
    </shadow-root>
  </div>
</body>
참고

예시에서는 가상의 shadow-root 요소를 사용하여 shadow root 인스턴스를 표현합니다.

#canvas1이 대상일 때 document.pointerLockElement#host1를, root1.pointerLockElement#canvas1을 반환합니다. 재타겟팅 결과 #canvas1#root2에 대해 재타겟팅하면 #host1가 되지만, #host1#root2와 같은 트리에 속하지 않으므로 root2.pointerLockElement는 null을 반환합니다.

6. MouseEvent 인터페이스의 확장

WebIDLpartial interface MouseEvent {
  readonly attribute double movementX;
  readonly attribute double movementY;
};
movementX 속성
movementY 속성

movementXmovementY 속성은 포인터 위치가 변화한 값을 제공합니다. 두 mousemove 이벤트 eNowePrevious 사이에 screenX, screenY 값을 저장하고, movementX = eNow.screenX - ePrevious.screenX과 같이 차이를 계산하는 것과 같습니다.

movementXmovementYmousemove 이벤트를 제외한 모든 마우스 이벤트에서는 0이어야 합니다. 모든 움직임 데이터는 mousemove 이벤트로 전달되어야 하며, 두 마우스 이벤트 earlierEventcurrentEvent 사이의 currentEvent.screenX - earlierEvent.screenX 값은 earlierEvent 이후 이벤트들의 movementX 합계와 같아야 합니다(단, 포인터가 사용자 에이전트 화면 경계에 의해 클립되어 screenX를 갱신하지 못하는 경우는 예외).

movementXmovementY는 포인터 잠금 상태와 관계없이 항상 갱신되어야 합니다.

잠금 해제 상태에서는 시스템 커서가 사용자 에이전트 창을 나갔다가 다시 진입할 수 있습니다. 이 경우 운영체제의 마우스 이동 이벤트의 대상이 사용자 에이전트가 아니면, 가장 최근의 포인터 위치를 사용자 에이전트가 알 수 없어 movementX / movementY 값을 계산할 수 없으므로 0으로 설정해야 합니다.

포인터 잠금이 활성화되면 clientX, clientY, screenX, screenY는 포인터가 전혀 움직이지 않은 것처럼 일정한 값을 유지해야 합니다. 하지만 movementXmovementY는 잠금 해제 상태와 동일하게 포인터 이동 변화를 계속 제공해야 하며, 마우스를 한 방향으로 계속 이동하면 movementXmovementY 값에 제한이 없습니다. 마우스 커서 개념이 제거되어 창을 벗어나거나 화면 경계에 의해 클램프되지 않습니다.

movementXmovementY의 초기화되지 않은 값은 0이어야 합니다.

마우스 입력이 중단되는 상황(예: 마우스 커서가 창을 나갔다가 다른 위치로 다시 진입)에서는 큰 movement 값이 나타나면 안 됩니다. 사용자 에이전트가 운영체제에서 마우스 입력 데이터를 받지 못하는 간격이 생기면, 다음 mousemove 이벤트는 movementXmovementY0으로 설정해야 합니다. 이러한 간격은 사용자 에이전트가 창 시스템 API에서 마우스 이탈 이벤트를 받을 때 등 발생할 수 있습니다. 예외적으로 마우스 캡처가 활성화되면 커서가 창 밖으로 이동해도 사용자 에이전트가 계속 마우스 이벤트를 받을 수 있습니다.

7. MouseEventInit 딕셔너리의 확장

WebIDLpartial dictionary MouseEventInit {
  double movementX = 0;
  double movementY = 0;
};
movementX 멤버
movementY 멤버
movementXmovementY 멤버는 각각 MouseEvent의 해당 멤버를 초기화하는 데 사용됩니다.

8. 요구사항

기본 잠금 해제 제스처는 항상 제공되어야 하며, 포인터 잠금 해제사용자 에이전트포인터-잠금 대상과 함께 수행해야 합니다.

참고

포인터-잠금 대상연결 해제되거나, 사용자 에이전트, 윈도우, 탭이 포커스를 잃으면 포인터 잠금은 해제되어야 합니다. 활성 문서(active document)의 요소들 사이, 또는 탐색 컨텍스트 사이로 포커스를 이동하는 것은 포인터 잠금 해제를 일으키지 않습니다. 예를 들어, 키보드로 프레임이나 iframe 내의 콘텐츠 사이로 포커스를 이동해도 해제되지 않습니다.

전체 화면 [FULLSCREEN]이 진입하거나 해제될 때, 포인터가 사용자 에이전트의 그래픽 UI와 상호작용해야 하거나, 기본 잠금 해제 제스처로 전체 화면과 포인터 잠금을 동시에 해제하거나, 윈도우나 탭 포커스를 잃은 경우를 제외하고 포인터 잠금이 해제되어서는 안 됩니다.

9. 사용 사례

이 절은 규범적이지 않습니다.

9.1 자유롭게 움직이는 가상 캐릭터의 상대적 뷰포트 회전

1인칭/3인칭 게임의 플레이어는 뷰포트 방향을 제어해야 합니다. 널리 사용되는 방법은 마우스 움직임으로 시야 각도를 조절하는 것입니다. 이런 종류의 애플리케이션은 Pointer Lock API를 사용하여 뷰포트의 요(yaw)와 피치(pitch)를 마우스 버튼을 누르지 않은 상태에서도 완전히 자유롭게 제어할 수 있습니다. 버튼은 다른 동작에 사용할 수 있고, 마우스 이동만으로 지속적으로 내비게이션이 가능합니다.

9.2 3D 모델의 자유 회전 또는 2D 레이어의 패닝

3차원 모델링 애플리케이션 사용자들은 모델을 회전시켜야 합니다. 애플리케이션은 Pointer Lock API를 사용하여 드래그 동작 중 움직임 제한 없이 모델을 자유롭게 회전시킬 수 있습니다. 포인터 잠금이 없으면 마우스 커서가 화면의 가장자리에서 움직임 데이터 제공이 멈춥니다.

마찬가지로, 큰 2차원 이미지를 단일 드래그 동작으로 커서/화면의 한계를 넘어서 절대 이동(panning)할 수도 있습니다.

9.3 캐릭터의 상대적 이동

빠른 반사신경이 요구되는 게임에서 플레이어는 패들을 조작해 공을 상대에게 튕겨내야 하는데, 패들은 다른 마우스 버튼에 따라 다양한 동작도 할 수 있습니다. 애플리케이션은 Pointer Lock API를 사용하여, 마우스 커서가 게임 영역을 벗어나 시스템의 다른 애플리케이션을 클릭해 게임 흐름이 중단되는 걱정 없이 플레이어가 빠르게 반응할 수 있게 해줍니다.

9.4 스피너 컨트롤에서의 조그 이동

애플리케이션에서 수치적인 값을 조절할 때, 사용자는 버튼 핸들을 드래그하여 값을 증감시키는 방식을 선호할 수 있습니다. 예를 들어, 숫자 입력 박스와 위/아래 화살표가 있는 스피너에서 화살표를 클릭하거나 드래그해 값을 바꾸는 경우입니다. 애플리케이션은 Pointer Lock API를 사용하여 논리적 화면 한계를 넘어서 값을 조절할 수 있게 합니다. 비디오 또는 오디오 스트림을 빠르게 감거나 되감는 "조그" 컨트롤에도 적용할 수 있습니다.

9.5 HTML DOM UI와의 합성 커서 상호작용

일부 게임에서는 고전적인 커서를 사용하지만, 커서의 이동을 제한하거나 제어하고자 할 수 있습니다. 예를 들어 게임 경계 안에서만 움직이거나, 게임에서 직접 커서를 이동시키는 경우입니다. 포인터를 잠그면 애플리케이션이 자체 커서를 만들 수 있고, HTML 및 DOM은 UI로 사용할 수 있습니다. 합성 마우스 이벤트를 허용하면 애플리케이션이 정의한 커서로 DOM과 상호작용할 수 있습니다. 예를 들어 아래 코드처럼 포인터가 잠긴 상태에서 커스텀 커서가 클릭 이벤트를 보낼 수 있습니다:

document.addEventListener("click", function (e) {
  if (e._isSynthetic)
    return;
  // send a synthetic click
  var ee = document.createEvent("MouseEvents");
  ee._isSynthetic = true;
  x = myCursor.x;
  y = myCursor.y;
  ee.initMouseEvent("click", true, true, null, 1,
                    x + e.screenX - e.clientX,
                    y + e.screenY - e.clientY,
                    x,
                    y);
  var target = document.elementFromPoint(x, y);
  if (target)
    target.dispatchEvent(ee);
});

합성 클릭은 사용자 에이전트에 의해 일반 클릭과 동일한 기본 동작을 허용하지 않을 수 있습니다. 하지만 애플리케이션 핸들러에서 추가 동작을 구현하거나 기존 HTML & DOM 메커니즘으로 UI를 제공할 수 있습니다.

9.6 뷰포트 경계에 마우스 커서를 이동하여 뷰포트 패닝

실시간 전략(RTS) 게임에서 자주 쓰이는 방식입니다. 플레이어가 포인터를 뷰포트의 경계까지 이동하면, 마우스 움직임으로 경계를 "밀면" 뷰포트가 게임 영역을 마우스 이동량만큼 패닝합니다. 뷰포트 경계 내에서 마우스 커서를 이동할 때는 시스템과 동일하게 동작합니다. 애플리케이션은 pointer lock과 "HTML DOM UI와의 합성 커서 상호작용" 사례를 함께 활용해 커서 동작을 완전히 제어할 수 있습니다.

9.7 게임 로비, 타이머 기반 포인터 잠금

포인터 잠금을 사용하는 게임은 플레이어가 게임 로비에서 준비하는 동안에는 일반 UI와 시스템 커서를 원할 수 있습니다. 모든 플레이어가 준비되면 보통 짧은 타이머 후 게임이 시작됩니다. 이상적으로는 게임이 사용자 활성화 없이 pointer lock 모드로 전환될 수 있어야 합니다. 플레이어는 게임 로비에서 게임 내비게이션으로 끊김 없이 이동할 수 있어야 합니다.

9.8 게임 포털

게임 포털 및 Facebook, Google Plus 같은 사이트는 사용자가 즐길 수 있는 게임을 호스팅합니다. 이 게임들은 포털 사이트와 다른 오리진에서 제공될 수 있습니다. 임베드된 게임은 전체 화면이 아니더라도 포인터를 잠글 수 있어야 합니다.

10. 보안

이 절은 규범적이지 않습니다.

보안 우려:

대응 방안:

권장 사항:

포인터 잠금은 특정 애플리케이션 유형에서 필수적인 사용자 상호작용 모드지만, 악의적으로 사용될 경우 사용성 문제가 있습니다. 공격자는 사용자가 시스템에서 마우스 커서를 제어할 수 있는 능력을 제거할 수 있습니다. 사용자 에이전트는 항상 포인터 잠금 해제가 가능한 메커니즘을 제공하고, 방법을 사용자에게 안내하며, 포인터 잠금을 활성화할 수 있는 조건을 제한함으로써 이를 방지합니다.

사용자 에이전트는 각 기기별 또는 사용자 옵션에 따라 자체적인 정책을 결정할 수 있습니다.

11. 자주 묻는 질문

이 절은 규범적이지 않습니다.

11.1 Mouse Capture의 setCapture()와 병합하지 않는 이유?

Mouse Capture [MDN-SETCAPTURE]는 마우스 드래그 동작 동안 마우스 이벤트 타겟을 잠그는 낮은 보안 위험의 기능입니다. 포인터 잠금은 커서 개념을 제거하고 모든 이벤트를 특정 타겟으로 전달합니다. 둘은 관련 있지만 다릅니다.

브라우저가 둘 다 구현한다면, "마우스 업 시 자동 잠금 해제"의 보안 단순성과 마우스 입력 전체 제어 및 시스템 커서 제거 기능을 조합할 수 있을 것입니다. 이 보안 특성은 드래그 이벤트 중 잠깐의 포인터 잠금만 필요한 애플리케이션에서 더 자유롭게 기능을 사용할 수 있게 해줍니다.

이 기능은 윈도우 모드에서 일부 마이너한 사용 사례에는 도움이 되지만, 주요 사용 사례를 해결하는 구현이 아직 없으므로 첫 버전 명세에서 제외되었습니다. 또한 브라우저가 둘 다 구현해야 하는데, 아직 그런 브라우저는 없습니다. 이 기능이 .lock에 있어야 할지 .setCapture에 있어야 할지 명확하지 않습니다. 둘 다 구현된다면, 어느 API든 하이브리드 기능을 쉽게 확장할 수 있습니다.

11.2 MouseEvent의 .clientX/Y, .screenX/Y를 재사용하지 않는 이유?

포인터가 잠금되지 않은 상태에서도 마우스 이동 델타 값은 유용합니다. 잠금 상태에 따라 .client 또는 .screen의 의미가 바뀌면, 잠금 상태를 신중히 관리하지 않는 코드에서 오류가 발생하기 쉽습니다.

11.3 .movementX/Y 대신 .deltaX/Y를 사용하지 않는 이유?

포인터 잠금 중에는 'wheel' 이벤트도 포인터-잠금 대상 요소에 'mousemove' 이벤트처럼 전달되어야 합니다. .deltaX/Y/Z는 DOM 3 'wheel' 이벤트에서 이미 정의되어 있어서 이름 충돌이 발생합니다.

11.4 모든 기능(커서 숨기기, 마우스 델타 제공 등)을 번들로 제공하고 CSS로 커서를 숨기거나 항상 델타 값을 제공하거나 웹 페이지 일부로 커서 이동을 제한하는 API를 제공하지 않는 이유?

보다 세분화된 접근을 제공하는 동기도 충분합니다. 예를 들어 "뷰포트 경계에 마우스 커서를 이동하여 뷰포트 패닝" 사례는 커서 숨김이 필요 없고, 커서를 경계 내에만 제한하며 항상 델타 값을 사용할 수 있으면 됩니다. 또한, 이 명세는 시스템 마우스 커서의 움직임에서 델타 값을 정의하는데, 이 값은 운영체제의 필터링 및 가속이 적용된 데이터입니다. 애플리케이션은 커서에 맞춘 조정 전의 좀 더 원시적인 움직임 데이터를 원할 수 있습니다. 원시 데이터는 픽셀 단위보다 더 정확한 움직임이나 더 높은 빈도의 업데이트도 가능하게 합니다. 원시 델타 제공은 사용자에게 특수 권한이나 모드가 필요하지 않으며, 커서 구속이 필요 없는 애플리케이션에서는 보안 장벽과 프롬프트를 줄일 수 있습니다.

이처럼 세분화된 접근을 미루는 두 가지 이유가 있습니다. 첫째는 마우스 움직임 데이터가 어떤 단위로 제공되는지를 명확히 해야 하는데, 명세는 .movementX/Y를 잠금 상태가 아닐 때 .screenX/Y 변화로 얻을 수 있는 값과 동일하게 정의합니다. 여러 사용자 에이전트와 운영체제에서 쉽게 일관성 있는 경험을 제공할 수 있습니다. 사용자는 이미 하드웨어 입력과 운영체제 옵션을 조정해 커서를 편하게 사용하도록 설정했으므로, 동일 단위의 .movementX/Y를 명세하면 모든 사용자에게 바로 쓸 수 있습니다.

둘째, 움직임 데이터 제공이나 마우스 커서 구속을 세분화된 방식으로 구현하는 것은 더 어렵습니다. 번들 기능을 제공하면 각 운영체제에 맞는 다양한 기법을 구현할 자유가 생기고 실제 구현이 더 쉽습니다. 주요 데스크탑 플랫폼(Windows, Mac OS X, Linux)에는 커서를 특정 사각형에 구속하는 직접 API가 없고, 이를 구현한 프로토타입도 없습니다. 예를 들어 보이지 않는 창Xlib, Mac에서 수동 커서 이동 등으로 시도해야 합니다. 가속되지 않은 델타 값은 HID(raw Human Interface Device) 데이터 읽기로 접근하는 방안이 있는데, 예를 들면 Windows의 WM_INPUT 메시지, Mac OS X/Linux의 USB 장치 API 등입니다. 여기서 단위를 해석하고 일관성 있게 정규화하는 것이 과제입니다. 그리고 대부분의 API는 USB 장치에만 한정됩니다.

향후 이러한 기능을 추가하는 것은 충분히 고려할 만하며, 현재 명세된 pointer lock API는 이러한 세분화된 델타 및 구속 기능이 구현되어도 계속 지원할 수 있습니다.

번들 API는 구현의 실용성, 원하는 사용 사례 지원, 그리고 향후 개선과 충돌하지 않기 때문에 선택되었습니다.

11.5 고해상도 델타 / 고빈도 업데이트?

아직 아닙니다. 이유는 앞선 질문과 동일합니다: "모든 기능(커서 숨기기, 마우스 델타 제공 등)을 번들로 제공하고 CSS로 커서를 숨기거나 항상 델타 값을 제공하거나 웹 페이지 일부로 커서 이동을 제한하는 API를 제공하지 않는 이유?".

11.6 MouseEvent를 수정하고 기존 마우스 이벤트를 재사용하는 대신 새로운 마우스 델타 이벤트를 만들지 않는 이유?

포인터 잠금 중에도 click, mousedown 등 많은 마우스 이벤트가 중요합니다. 이들은 모두 동일한 MouseEvent 데이터 구조를 공유합니다. 만약 움직임 데이터를 새로운 데이터 구조로 보고하면, 델타 움직임을 보고하는 새로운 이벤트가 필요합니다. 새로운 구조는 MouseEvent와 동일한 편의성을 제공하려면, 버튼 및 수정키 상태 등도 포함해야 합니다. click, down, up 이벤트 처리에 기존 mousedown, mouseup을 사용할 것이라면 .clientX/Y와 .screenX/Y에는 쓸모 없는 값이 들어가지만, 현재 움직임 데이터가 빠집니다. 아니면 마우스 잠금 중에만 새로운 이벤트도 필요하게 됩니다.

또한, movementX/Y는 마우스가 잠금되지 않은 상태에서도 편리합니다. 이 명세는 커서가 존재할 때도 movement 속성이 항상 유효해야 한다고 요구합니다. 이로써 애플리케이션이 델타 움직임을 활용하려면 마지막 커서 상태나 mouseover/mouseout 전환 관리 코드를 줄일 수 있습니다.

MouseEvent에 movementX/Y를 추가하면 포인터 잠금 중 clientX/Y와 screenX/Y 값이 사용되지 않는다는 점만 단점입니다. 하지만 이는 큰 문제가 아니라고 판단됩니다.

따라서 API 및 구현 복잡도를 줄이기 위해 MouseEvent에 movementX/Y를 최소한의 변경으로 추가하는 방식을 선택했습니다.

11.7 포인터 잠금 상태의 마우스 이벤트와 키보드 입력 포커스에 대해 별도의 타겟을 사용하는 이유?

마우스 커서로 3D 뷰를 제어하면서 텍스트 콘솔로 다른 사용자와 채팅하는 게임을 생각해보면, 마우스 이벤트가 전달되는 요소와 키보드 입력을 받는 요소가 서로 다른 것이 합리적입니다. 이는 폼에 입력하면서 페이지의 어떤 요소에도 mousemove 이벤트가 전달되는 기존 동작과 유사합니다.

12. 준수

비규범적으로 표시된 섹션뿐만 아니라, 이 명세서의 모든 작성 지침, 다이어그램, 예시, 참고 사항은 비규범적입니다. 그 외 모든 내용은 규범적입니다.

이 명세서는 하나의 제품에 적용되는 준수 기준을 정의합니다: 해당 명세서에 포함된 인터페이스를 구현하는 사용자 에이전트입니다.

A. 감사의 글

이 절은 비규범적입니다.

이 명세서의 논의에 기여해 주신 많은 분들께 감사드립니다:

B. 참고 문헌

B.1 규범적 참고 문헌

[dom]
DOM 표준. Anne van Kesteren. WHATWG. 현행 표준. URL: https://dom.spec.whatwg.org/
[FULLSCREEN]
Fullscreen API 표준. Philip Jägenstedt. WHATWG. 현행 표준. URL: https://fullscreen.spec.whatwg.org/
[html]
HTML 표준. Anne van Kesteren; Domenic Denicola; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. 현행 표준. URL: https://html.spec.whatwg.org/multipage/
[ui-events]
UI Events. Gary Kacmarcik; Travis Leithead. W3C. 2024년 2월 21일. W3C 워킹 드래프트. URL: https://www.w3.org/TR/uievents/
[WEBIDL]
Web IDL 표준. Edgar Chen; Timothy Gu. WHATWG. 현행 표준. URL: https://webidl.spec.whatwg.org/

B.2 설명적 참고 문헌

[MDN-SETCAPTURE]
Element.setCapture(). Eric Shepherd; et. al. URL: https://developer.mozilla.org/en-US/docs/Web/API/Element/setCapture