사이트에 터치 추가

터치스크린은 휴대전화에서 데스크톱 화면에 이르기까지 점점 더 많은 기기에서 사용할 수 있습니다. 앱은 직관적이고 멋진 방식으로 터치에 반응해야 합니다.

터치스크린은 휴대전화에서 데스크톱 화면에 이르기까지 점점 더 많은 기기에서 사용할 수 있습니다. 사용자가 UI와 상호작용하도록 선택하면 앱은 직관적인 방식으로 터치에 반응해야 합니다.

요소 상태에 응답

웹페이지의 어떤 요소를 터치하거나 클릭했을 때 사이트에서 실제로 이 요소를 감지했는지 궁금했던 적이 있나요?

사용자가 UI의 일부를 터치하거나 상호작용할 때 요소의 색상을 변경하기만 해도 사이트가 작동하는지 확인할 수 있습니다. 이렇게 하면 불만이 완화될 뿐만 아니라 빠르고 반응성이 뛰어난 느낌을 줄 수 있습니다.

DOM 요소는 기본값, 포커스, 마우스 오버, 활성 상태와 같은 상태를 상속할 수 있습니다. 이러한 각 상태의 UI를 변경하려면 아래와 같이 의사 클래스 :hover, :focus, :active에 스타일을 적용해야 합니다.

.btn {
  background-color: #4285f4;
}

.btn:hover {
  background-color: #296cdb;
}

.btn:focus {
  background-color: #0f52c1;

  /* The outline parameter suppresses the border
  color / outline when focused */
  outline: 0;
}

.btn:active {
  background-color: #0039a8;
}

사용해 보기

버튼 상태의 다양한 색상을 보여주는 이미지

대부분의 모바일 브라우저에서는 요소를 탭한 후 hoverhover 상태가 요소에 적용됩니다.

어떤 스타일을 설정할지, 사용자가 스타일을 터치한 후 어떻게 보일지 신중하게 고려하세요.

기본 브라우저 스타일 숨기기

여러 상태의 스타일을 추가하면 대부분의 브라우저에서 사용자 터치에 반응하여 자체 스타일을 구현합니다. 이는 휴대기기가 처음 실행될 때 여러 사이트에 :active 상태의 스타일이 없기 때문입니다. 따라서 많은 브라우저에서 사용자 의견을 제공하기 위해 강조표시 색상이나 스타일을 추가했습니다.

대부분의 브라우저는 요소에 포커스가 있을 때 outline CSS 속성을 사용하여 요소 주위에 링을 표시합니다. 다음을 사용하여 억제할 수 있습니다.

.btn:focus {
    outline: 0;

    /* Add replacement focus styling here (i.e. border) */
}

Safari 및 Chrome에서는 -webkit-tap-highlight-color CSS 속성으로 방지할 수 있는 탭 강조표시 색상을 추가합니다.

/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
  -webkit-tap-highlight-color: transparent;
}

사용해 보기

Windows Phone의 Internet Explorer도 동작은 비슷하지만 다음과 같은 메타 태그를 통해 차단됩니다.

<meta name="msapplication-tap-highlight" content="no">

Firefox에는 두 가지 부작용이 있습니다.

터치 가능한 요소에 윤곽선을 추가하는 -moz-focus-inner 유사 클래스는 border: 0를 설정하여 삭제할 수 있습니다.

Firefox에서 <button> 요소를 사용하는 경우 그라데이션이 적용됩니다. background-image: none를 설정하여 그라데이션을 삭제할 수 있습니다.

/* Firefox Specific CSS to remove button
differences and focus ring */
.btn {
  background-image: none;
}

.btn::-moz-focus-inner {
  border: 0;
}

사용해 보기

사용자 선택 사용 중지

UI를 만들 때 사용자가 요소와 상호작용하기를 원하지만 길게 눌러 텍스트를 선택하거나 마우스를 UI 위로 드래그하는 기본 동작을 억제하려는 경우가 있을 수 있습니다.

user-select CSS 속성을 사용하여 이 작업을 할 수 있지만, 사용자가 요소에서 텍스트를 선택하려는 경우 콘텐츠에서 이 작업을 실행하면 extremely 화가 나는 것일 수 있습니다. 따라서 이 기능을 사용할 때는 주의하고 드물게 사용해야 합니다.

/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
  user-select: none;
}

맞춤 동작 구현

사이트의 맞춤 상호작용과 동작에 관한 아이디어가 있다면 다음 두 가지 주제를 염두에 두어야 합니다.

  1. 모든 브라우저를 지원하는 방법
  2. 프레임 속도를 높게 유지하는 방법

이 도움말에서는 모든 브라우저를 지원하는 데 필요한 API와 이러한 이벤트를 효율적으로 사용하는 방법을 알아봅니다.

동작으로 실행하려는 작업에 따라 사용자가 한 번에 하나의 요소와 상호작용하도록 할 수도 있고 또는 동시에 여러 요소와 상호작용할 수 있도록 할 수도 있습니다.

이 도움말의 두 가지 예, 즉 모든 브라우저를 지원하는 방법과 프레임 속도를 높게 유지하는 방법을 살펴보겠습니다.

문서 터치에 대한 예시 GIF

첫 번째 예를 사용하면 사용자가 하나의 요소와 상호작용할 수 있습니다. 이 경우 동작이 처음에 요소 자체에서 시작되었다면 모든 터치 이벤트가 이 요소에 제공되도록 할 수 있습니다. 예를 들어 스와이프 가능 요소에서 손가락을 이동해도 여전히 요소를 제어할 수 있습니다.

이는 사용자에게 상당한 유연성을 제공하지만 사용자가 UI와 상호작용할 수 있는 방식을 제한합니다.

요소 터치의 예시 GIF

그러나 사용자가 멀티 터치를 사용하여 동시에 여러 요소와 상호작용해야 한다면 터치를 특정 요소로 제한해야 합니다.

이는 사용자에게 더 유연하지만 UI 조작을 위한 로직이 복잡하고 사용자 오류에 대한 복원력이 떨어집니다.

이벤트 리스너 추가

Chrome (버전 55 이상), Internet Explorer, Edge에서는 PointerEvents를 사용하여 맞춤 동작을 구현하는 것이 좋습니다.

다른 브라우저에서는 TouchEventsMouseEvents가 올바른 방법입니다.

PointerEvents의 유용한 기능은 마우스, 터치, 펜 이벤트를 비롯한 여러 유형의 입력을 하나의 콜백 세트로 병합한다는 것입니다. 리슨할 이벤트는 pointerdown, pointermove, pointerup, pointercancel입니다.

다른 브라우저의 상응하는 기능은 터치 이벤트의 경우 touchstart, touchmove, touchend, touchcancel이며, 마우스 입력에 동일한 동작을 구현하려면 mousedown, mousemove, mouseup를 구현해야 합니다.

사용할 이벤트에 관해 궁금한 점이 있으면 터치, 마우스, 포인터 이벤트 표를 확인하세요.

이러한 이벤트를 사용하려면 DOM 요소에서 이벤트 이름, 콜백 함수, 부울과 함께 addEventListener() 메서드를 호출해야 합니다. 불리언은 다른 요소가 이벤트를 포착하고 해석하기 전에 이벤트를 포착해야 할지, 아니면 후에 포착할지 결정합니다. true는 이벤트를 다른 요소보다 먼저 배치하고자 함을 의미합니다.

다음은 상호작용 시작을 수신 대기하는 예입니다.

// Check if pointer events are supported.
if (window.PointerEvent) {
  // Add Pointer Event Listener
  swipeFrontElement.addEventListener('pointerdown', this.handleGestureStart, true);
  swipeFrontElement.addEventListener('pointermove', this.handleGestureMove, true);
  swipeFrontElement.addEventListener('pointerup', this.handleGestureEnd, true);
  swipeFrontElement.addEventListener('pointercancel', this.handleGestureEnd, true);
} else {
  // Add Touch Listener
  swipeFrontElement.addEventListener('touchstart', this.handleGestureStart, true);
  swipeFrontElement.addEventListener('touchmove', this.handleGestureMove, true);
  swipeFrontElement.addEventListener('touchend', this.handleGestureEnd, true);
  swipeFrontElement.addEventListener('touchcancel', this.handleGestureEnd, true);

  // Add Mouse Listener
  swipeFrontElement.addEventListener('mousedown', this.handleGestureStart, true);
}

사용해 보기

단일 요소 상호작용 처리

위의 짧은 코드 스니펫에는 마우스 이벤트의 시작 이벤트 리스너만 추가되었습니다. 그 이유는 마우스 이벤트가 이벤트 리스너가 추가된 요소 위에 마우스를 가져갈 때만 트리거되기 때문입니다.

TouchEvents는 터치가 발생한 위치와 관계없이 동작이 시작된 후 동작을 추적하고 PointerEvents는 DOM 요소에서 setPointerCapture를 호출한 후 터치가 발생한 위치와 관계없이 이벤트를 추적합니다.

마우스 이동 및 종료 이벤트의 경우 동작 시작 메서드 에 이벤트 리스너를 추가하고 이 리스너를 문서에 추가합니다. 즉, 동작이 완료될 때까지 커서를 추적할 수 있습니다.

이를 구현하는 단계는 다음과 같습니다.

  1. 모든 TouchEvent 및 PointerEvent 리스너를 추가합니다. MouseEvents의 경우 시작 이벤트 추가합니다.
  2. 시작 동작 콜백 내에서 마우스 이동 및 종료 이벤트를 문서에 바인딩합니다. 이렇게 하면 이벤트가 원래 요소에서 발생하는지와 관계없이 모든 마우스 이벤트가 수신됩니다. PointerEvents의 경우 추가 이벤트를 모두 수신하려면 원래 요소에서 setPointerCapture()를 호출해야 합니다. 그런 다음 동작의 시작을 처리합니다.
  3. 이동 이벤트를 처리합니다.
  4. 종료 이벤트에서 문서에서 마우스 이동 및 종료 리스너를 삭제하고 동작을 종료합니다.

다음은 이동 및 종료 이벤트를 문서에 추가하는 handleGestureStart() 메서드의 스니펫입니다.

// Handle the start of gestures
this.handleGestureStart = function(evt) {
  evt.preventDefault();

  if(evt.touches && evt.touches.length > 1) {
    return;
  }

  // Add the move and end listeners
  if (window.PointerEvent) {
    evt.target.setPointerCapture(evt.pointerId);
  } else {
    // Add Mouse Listeners
    document.addEventListener('mousemove', this.handleGestureMove, true);
    document.addEventListener('mouseup', this.handleGestureEnd, true);
  }

  initialTouchPos = getGesturePointFromEvent(evt);

  swipeFrontElement.style.transition = 'initial';
}.bind(this);

사용해 보기

추가하는 종료 콜백은 handleGestureEnd()입니다. 이 콜백은 문서에서 이동 및 종료 이벤트 리스너를 삭제하고 동작이 다음과 같이 완료되면 포인터 캡처를 해제합니다.

// Handle end gestures
this.handleGestureEnd = function(evt) {
  evt.preventDefault();

  if (evt.touches && evt.touches.length > 0) {
    return;
  }

  rafPending = false;

  // Remove Event Listeners
  if (window.PointerEvent) {
    evt.target.releasePointerCapture(evt.pointerId);
  } else {
    // Remove Mouse Listeners
    document.removeEventListener('mousemove', this.handleGestureMove, true);
    document.removeEventListener('mouseup', this.handleGestureEnd, true);
  }

  updateSwipeRestPosition();

  initialTouchPos = null;
}.bind(this);

사용해 보기

문서에 이동 이벤트를 추가하는 이 패턴을 따르면 사용자가 요소와 상호작용을 시작하고 동작을 요소 외부로 이동하면 이벤트가 문서로부터 수신되므로 페이지의 위치에 관계없이 마우스 움직임이 계속 발생합니다.

이 다이어그램은 동작이 시작되면 문서에 이동 및 종료 이벤트를 추가할 때 터치 이벤트가 어떻게 작동하는지 보여줍니다.

&#39;touchstart&#39;의 문서에 터치 이벤트 결합

효율적인 터치 반응

이제 시작 및 종료 이벤트를 처리했으므로 실제로 터치 이벤트에 응답할 수 있습니다.

모든 시작 및 이동 이벤트에 대해 이벤트에서 xy를 쉽게 추출할 수 있습니다.

다음 예에서는 targetTouches가 있는지 확인하여 이벤트가 TouchEvent에서 발생했는지 확인합니다. 포함하는 경우 첫 번째 터치에서 clientXclientY를 추출합니다. 이벤트가 PointerEvent 또는 MouseEvent인 경우 이벤트 자체에서 직접 clientXclientY를 추출합니다.

function getGesturePointFromEvent(evt) {
    var point = {};

    if (evt.targetTouches) {
      // Prefer Touch Events
      point.x = evt.targetTouches[0].clientX;
      point.y = evt.targetTouches[0].clientY;
    } else {
      // Either Mouse event or Pointer Event
      point.x = evt.clientX;
      point.y = evt.clientY;
    }

    return point;
  }

사용해 보기

TouchEvent에는 터치 데이터가 포함된 세 개의 목록이 있습니다.

  • touches: 터치하는 DOM 요소와 관계없이 화면의 모든 현재 터치 목록입니다.
  • targetTouches: 이벤트가 바인딩된 DOM 요소에 있는 현재 터치 목록입니다.
  • changedTouches: 변경되어 이벤트가 실행된 터치의 목록입니다.

대부분의 경우 targetTouches는 개발자가 필요로 하고 원하는 모든 것을 제공합니다. 이러한 목록에 관한 자세한 내용은 터치 목록을 참고하세요.

requestAnimationFrame 사용

이벤트 콜백은 기본 스레드에서 실행되므로 이벤트 콜백에서 코드를 가능한 한 적게 실행하여 프레임 속도를 높게 유지하고 버벅거림을 방지하려고 합니다.

requestAnimationFrame()를 사용하면 브라우저가 프레임을 그리기 직전에 UI를 업데이트할 수 있으며, 이벤트 콜백에서 작업을 이동하는 데 도움이 됩니다.

requestAnimationFrame()에 익숙하지 않은 경우 여기에서 자세히 알아보세요.

일반적인 구현은 시작 및 이동 이벤트에서 xy 좌표를 저장하고 이동 이벤트 콜백 내에서 애니메이션 프레임을 요청하는 것입니다.

이 데모에서는 초기 터치 위치를 handleGestureStart()에 저장합니다 (initialTouchPos 찾기).

// Handle the start of gestures
this.handleGestureStart = function(evt) {
  evt.preventDefault();

  if (evt.touches && evt.touches.length > 1) {
    return;
  }

  // Add the move and end listeners
  if (window.PointerEvent) {
    evt.target.setPointerCapture(evt.pointerId);
  } else {
    // Add Mouse Listeners
    document.addEventListener('mousemove', this.handleGestureMove, true);
    document.addEventListener('mouseup', this.handleGestureEnd, true);
  }

  initialTouchPos = getGesturePointFromEvent(evt);

  swipeFrontElement.style.transition = 'initial';
}.bind(this);

handleGestureMove() 메서드는 필요한 경우 애니메이션 프레임을 요청하기 전에 이벤트의 위치를 저장하고 onAnimFrame() 함수를 콜백으로 전달합니다.

this.handleGestureMove = function (evt) {
  evt.preventDefault();

  if (!initialTouchPos) {
    return;
  }

  lastTouchPos = getGesturePointFromEvent(evt);

  if (rafPending) {
    return;
  }

  rafPending = true;

  window.requestAnimFrame(onAnimFrame);
}.bind(this);

onAnimFrame 값은 호출 시 UI를 이동하도록 변경하는 함수입니다. 이 함수를 requestAnimationFrame()에 전달하면 페이지를 업데이트(즉, 페이지의 변경사항을 페인트)하기 직전에 이 함수를 호출하도록 브라우저에 지시합니다.

handleGestureMove() 콜백에서 먼저 rafPending가 false인지 확인합니다. 이는 마지막 이동 이벤트 이후 requestAnimationFrame()onAnimFrame()를 호출했는지 나타냅니다. 즉, 실행 대기 중인 requestAnimationFrame()는 한 번에 하나만 있습니다.

onAnimFrame() 콜백이 실행되면 rafPendingfalse로 업데이트하기 전에 이동하려는 요소에 변환을 설정하여 다음 터치 이벤트에서 새 애니메이션 프레임을 요청할 수 있습니다.

function onAnimFrame() {
  if (!rafPending) {
    return;
  }

  var differenceInX = initialTouchPos.x - lastTouchPos.x;
  var newXTransform = (currentXPosition - differenceInX)+'px';
  var transformStyle = 'translateX('+newXTransform+')';

  swipeFrontElement.style.webkitTransform = transformStyle;
  swipeFrontElement.style.MozTransform = transformStyle;
  swipeFrontElement.style.msTransform = transformStyle;
  swipeFrontElement.style.transform = transformStyle;

  rafPending = false;
}

터치 작업을 사용하여 동작 제어

CSS 속성 touch-action를 사용하면 요소의 기본 터치 동작을 제어할 수 있습니다. 이 예에서는 touch-action: none를 사용하여 브라우저가 사용자 터치로 아무것도 하지 못하도록 하여 모든 터치 이벤트를 가로챌 수 있습니다.

/* Pass all touches to javascript: */
button.custom-touch-logic {
  touch-action: none;
}

touch-action: none를 사용하는 것은 모든 기본 브라우저 동작을 차단하므로 다소 안전한 옵션입니다. 대부분의 경우 아래 옵션 중 하나가 더 나은 해결 방법입니다.

touch-action를 사용하면 브라우저에서 구현한 동작을 사용 중지할 수 있습니다. 예를 들어 IE10 이상에서는 두 번 탭하여 확대/축소 동작을 지원합니다. touch-actionmanipulation로 설정하면 기본적인 두 번 탭 동작이 방지됩니다.

이렇게 하면 두 번 탭 동작을 직접 구현할 수 있습니다.

다음은 일반적으로 사용되는 touch-action 값의 목록입니다.

터치 액션 매개변수
touch-action: none 브라우저에서 터치 상호작용은 처리하지 않습니다.
touch-action: pinch-zoom 브라우저에서 계속 처리하는 'pnch-zoom'을 제외하고 'touch-action: none'과 같은 모든 브라우저 상호작용이 사용 중지됩니다.
touch-action: pan-y pinch-zoom 세로 스크롤이나 손가락 모으기/확대/축소 (예: 이미지 캐러셀)를 사용 중지하지 않고 JavaScript에서 가로 스크롤을 처리합니다.
touch-action: manipulation 브라우저의 클릭 지연을 방지하는 두 번 탭 동작을 사용 중지합니다. 스크롤 및 손가락 모으기/확대/축소를 브라우저에 그대로 둡니다.

이전 버전의 IE 지원

IE10을 지원하려면 공급업체 접두사가 붙은 PointerEvents의 버전을 처리해야 합니다.

PointerEvents 지원 여부를 확인하려면 일반적으로 window.PointerEvent를 찾지만 IE10에서는 window.navigator.msPointerEnabled를 찾습니다.

공급업체 접두사가 있는 이벤트 이름은 'MSPointerDown', 'MSPointerUp', 'MSPointerMove'입니다.

아래 예는 지원 여부를 확인하고 이벤트 이름을 전환하는 방법을 보여줍니다.

var pointerDownName = 'pointerdown';
var pointerUpName = 'pointerup';
var pointerMoveName = 'pointermove';

if (window.navigator.msPointerEnabled) {
  pointerDownName = 'MSPointerDown';
  pointerUpName = 'MSPointerUp';
  pointerMoveName = 'MSPointerMove';
}

// Simple way to check if some form of pointerevents is enabled or not
window.PointerEventsSupport = false;
if (window.PointerEvent || window.navigator.msPointerEnabled) {
  window.PointerEventsSupport = true;
}

자세한 내용은 Microsoft의 업데이트 도움말을 참고하세요.

참조

터치 상태를 위한 유사 클래스

클래스 설명
:hover
눌린 상태의 버튼
커서를 요소 위에 놓을 때 입력됩니다. 마우스 오버 시 UI를 변경하면 사용자가 요소와 상호작용하도록 유도하는 데 도움이 됩니다.
:포커스
포커스 상태가 있는 버튼
사용자가 페이지의 요소를 탭할 때 입력됩니다. 포커스 상태를 통해 사용자는 현재 상호작용 중인 요소를 알 수 있습니다. 또한 사용자가 키보드를 사용하여 UI를 쉽게 탐색할 수 있습니다.
:활성
눌린 상태의 버튼
요소가 선택 중일 때(예: 사용자가 요소를 클릭하거나 터치할 때) 입력됩니다.

터치 이벤트에 대한 자세한 참고 자료는 W3C 터치 이벤트에서 확인할 수 있습니다.

터치, 마우스 및 포인터 이벤트

다음 이벤트는 애플리케이션에 새 동작을 추가하기 위한 기본 요소입니다.

터치, 마우스, 포인터 이벤트
touchstart, mousedown, pointerdown 이 메서드는 손가락이 요소를 처음 터치하거나 사용자가 마우스를 클릭할 때 호출됩니다.
touchmove, mousemove, pointermove 이 메서드는 사용자가 화면에서 손가락을 이동하거나 마우스로 드래그할 때 호출됩니다.
touchend, mouseup, pointerup 이 메서드는 사용자가 화면에서 손가락을 떼거나 마우스에서 손을 떼면 호출됩니다.
touchcancel pointercancel 이 메서드는 브라우저가 터치 동작을 취소할 때 호출됩니다. 예를 들어 사용자가 웹 앱을 터치한 후 탭을 변경합니다.

터치 목록

각 터치 이벤트에는 세 개의 목록 속성이 포함됩니다.

터치 이벤트 속성
touches 터치 중인 요소와 관계없이 화면의 모든 현재 터치 목록입니다.
targetTouches 현재 이벤트의 타겟인 요소에서 시작된 터치 목록입니다. 예를 들어 <button>에 바인딩하면 이 버튼의 현재 터치만 표시됩니다. 문서에 바인딩하는 경우 현재 문서에 적용된 모든 터치가 적용됩니다.
changedTouches 변경되어 이벤트 실행으로 이어진 터치 목록:
  • touchstart 이벤트의 경우 -- 현재 이벤트로 방금 활성화된 터치 포인트의 목록입니다.
  • touchmove 이벤트의 경우 -- 마지막 이벤트 이후로 이동한 터치 포인트의 목록입니다.
  • touchend touchcancel 이벤트의 경우 -- 노출 영역에서 방금 삭제된 터치 포인트의 목록입니다.

iOS에서 활성 상태 지원 사용 설정

iOS의 Safari에서는 기본적으로 활성 상태를 적용하지 않습니다. 그러려면 문서 본문 또는 각 요소에 touchstart 이벤트 리스너를 추가해야 합니다.

이 작업은 iOS 기기에서만 실행되도록 사용자 에이전트 테스트 후에 수행해야 합니다.

본문에 터치 시작을 추가하면 DOM의 모든 요소에 적용되는 이점이 있지만 페이지를 스크롤할 때 성능 문제가 발생할 수 있습니다.

window.onload = function() {
  if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
    document.body.addEventListener('touchstart', function() {}, false);
  }
};

대안으로는 페이지에서 상호작용할 수 있는 모든 요소에 터치 스타트 리스너를 추가하여 성능 문제를 일부 완화할 수 있습니다.

window.onload = function() {
  if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
    var elements = document.querySelectorAll('button');
    var emptyFunction = function() {};

    for (var i = 0; i < elements.length; i++) {
        elements[i].addEventListener('touchstart', emptyFunction, false);
    }
  }
};