나아갈 방향을 가리키기

Sérgio Gomes

이전에는 웹에서 사물을 가리키는 것이 간단했습니다. 마우스를 사용하고, 이리저리 움직이고, 버튼을 누르는 것만으로도 충분했습니다. 마우스가 아닌 모든 것은 하나로 에뮬레이션되었으며 개발자는 무엇을 기대할 수 있는지 정확하게 알고 있었습니다.

하지만 단순하다고 해서 무조건 좋은 것은 아닙니다. 시간이 지남에 따라 모든 것이 마우스가 아닌 것으로 (또는 마우스라고 가장하지) 않는 것이 점점 더 중요해지게 되었습니다. 압력에 민감하고 기울기를 인식하는 펜을 사용하여 창의력을 마음껏 발휘할 수 있고, 손가락으로 기기와 손만 사용할 수 있습니다. 그리고 있을 때는 손가락 두 개 이상 사용하지 말아야 합니다.

한동안 이 작업에 도움이 되도록 터치 이벤트를 사용해 왔지만, 터치와 관련해서는 완전히 별개의 API이므로 마우스와 터치를 모두 지원하려면 별도의 이벤트 모델 두 개를 코딩해야 합니다. Chrome 55에는 포인터 이벤트라는 두 모델을 통합하는 최신 표준이 함께 제공됩니다.

단일 이벤트 모델

포인터 이벤트는 브라우저의 포인터 입력 모델을 통합하여 터치, 펜, 마우스를 단일 이벤트 집합으로 가져옵니다. 예를 들면 다음과 같습니다.

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

다음은 사용 가능한 모든 이벤트의 목록입니다. 마우스 이벤트에 익숙하다면 매우 익숙할 것입니다.

pointerover 포인터가 요소의 경계 상자에 들어갔습니다. 이는 마우스 오버를 지원하는 기기의 경우 즉시 또는 지원되지 않는 기기의 경우 pointerdown 이벤트 전에 발생합니다.
pointerenter pointerover와 비슷하지만 도움말 풍선이 아니고 하위 요소를 다르게 처리합니다. 사양 세부정보
pointerdown 포인터가 입력 기기의 시맨틱스에 따라 버튼을 누르거나 접촉이 설정된 상태로 활성 버튼 상태에 들어갔습니다.
pointermove 포인터 위치가 변경되었습니다.
pointerup 포인터가 활성 버튼 상태를 벗어났습니다.
pointercancel 포인터가 더 이상 이벤트를 내보낼 가능성이 낮다는 의미입니다. 즉, 진행 중인 작업을 취소하고 중립 입력 상태로 돌아가야 합니다.
pointerout 포인터가 요소 또는 화면의 경계 상자를 벗어났습니다. 또한 기기에서 마우스 오버를 지원하지 않는 경우 pointerup 이후
pointerleave pointerout와 비슷하지만 도움말 풍선이 아니고 하위 요소를 다르게 처리합니다. 사양 세부정보
gotpointercapture 요소가 포인터 캡처를 수신했습니다.
lostpointercapture 캡처 중이었던 포인터가 해제되었습니다.

다양한 입력 유형

일반적으로 포인터 이벤트를 사용하면 여러 입력 기기에 별도의 이벤트 핸들러를 등록하지 않고도 입력에 구애받지 않는 방식으로 코드를 작성할 수 있습니다. 물론, 여전히 마우스 오버 개념의 적용 여부와 같은 입력 유형 간 차이점에 유의해야 합니다. 다양한 입력 기기 유형을 구분하고 싶다면(예: 다양한 입력에 별도의 코드/기능을 제공하기 위해) PointerEvent 인터페이스의 pointerType 속성을 사용하여 동일한 이벤트 핸들러 내에서 구별할 수 있습니다. 예를 들어 측면 탐색 창을 코딩하고 있다면 pointermove 이벤트에 다음과 같은 로직을 사용할 수 있습니다.

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

기본 작업

터치 지원 브라우저에서는 특정 동작을 사용하여 페이지를 스크롤하거나 확대/축소하거나 새로고침합니다. 터치 이벤트의 경우 이러한 기본 작업이 실행되는 동안 계속 이벤트를 수신합니다. 예를 들어 touchmove는 사용자가 스크롤하는 동안 계속 실행됩니다.

포인터 이벤트를 사용하면 스크롤 또는 확대/축소와 같은 기본 작업이 트리거될 때마다 pointercancel 이벤트가 발생하여 브라우저가 포인터를 제어했음을 알 수 있습니다. 예를 들면 다음과 같습니다.

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

기본 제공 속도: 이 모델은 동일한 수준의 응답성을 달성하기 위해 패시브 이벤트 리스너를 사용해야 하는 터치 이벤트에 비해 기본적으로 더 나은 성능을 제공합니다.

touch-action CSS 속성을 사용하여 브라우저가 제어하지 못하도록 할 수 있습니다. 요소에서 이 속성을 none로 설정하면 해당 요소에서 시작된 모든 브라우저 정의 작업이 사용 중지됩니다. 그러나 더 세부적인 제어를 위한 다른 값도 많이 있습니다(예: pan-x). 이 값을 사용하면 브라우저가 x축에서의 움직임에는 반응하고 y축의 움직임에는 반응하지 않도록 할 수 있습니다. Chrome 55는 다음 값을 지원합니다.

auto 기본값. 브라우저에서 모든 기본 작업을 수행할 수 있습니다.
none 브라우저에서 기본 작업을 실행할 수 없습니다.
pan-x 브라우저에서는 가로 스크롤 기본 작업만 수행할 수 있습니다.
pan-y 브라우저에서는 세로 스크롤 기본 작업만 수행할 수 있습니다.
pan-left 브라우저에서 가로 스크롤 기본 작업만 실행하고 페이지를 왼쪽으로 화면 이동만 할 수 있습니다.
pan-right 브라우저에서 가로 스크롤 기본 작업만 실행하고 페이지를 오른쪽으로 화면 이동만 할 수 있습니다.
pan-up 브라우저에서 세로 스크롤 기본 작업만 실행하고 페이지를 위로 화면 이동만 할 수 있습니다.
pan-down 브라우저에서 세로 스크롤 기본 작업만 실행하고 페이지를 아래로 화면 이동만 할 수 있습니다.
manipulation 브라우저에서 스크롤 및 확대/축소 작업만 수행할 수 있습니다.

포인터 캡처

사용자가 클릭 타겟 외부의 버튼을 놓기 때문이라는 것을 깨닫고 오류가 발생한 mouseup 이벤트를 디버깅하느라 고생한 적이 있으신가요? 없으시다면 음, 그럼 저 혼자일 수도 있겠네요.

하지만 지금까지는 이 문제를 해결할 좋은 방법이 없었습니다. 물론 문서에 mouseup 핸들러를 설정하고 애플리케이션에 일부 상태를 저장하여 상황을 추적할 수 있습니다. 그러나 이것이 가장 깔끔한 솔루션은 아니지만 웹 구성요소를 빌드하고 모든 요소를 잘 격리된 상태로 유지하려는 경우에 특히 그렇습니다.

포인터 이벤트를 사용하면 훨씬 더 나은 해결책이 제공됩니다. 포인터를 캡처하여 pointerup 이벤트 (또는 잘 알아내지 못한 다른 친구)를 확실히 가져올 수 있습니다.

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

브라우저 지원

이 문서 작성 시점을 기준으로 포인터 이벤트는 Internet Explorer 11, Microsoft Edge, Chrome, Opera에서 지원되고 Firefox에서는 부분적으로 지원됩니다. caniuse.com에서 최신 목록을 확인할 수 있습니다.

포인터 이벤트 폴리필을 사용하여 공백을 채울 수 있습니다. 또는 런타임 시 브라우저 지원을 확인하는 방법은 간단합니다.

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

포인터 이벤트는 점진적 개선을 위한 좋은 후보입니다. 초기화 메서드를 수정하여 위 내용을 확인하고 if 블록에 포인터 이벤트 핸들러를 추가한 다음 마우스/터치 이벤트 핸들러를 else 블록으로 이동하면 됩니다.

기능을 사용해 보고 의견을 알려주세요.