터치스크린은 휴대전화에서 데스크톱 화면에 이르기까지 점점 더 많은 기기에서 사용할 수 있습니다. 앱은 직관적이고 멋진 방식으로 터치에 반응해야 합니다.
터치스크린은 휴대전화에서 데스크톱 화면에 이르기까지 점점 더 많은 기기에서 사용할 수 있습니다. 사용자가 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;
}
대부분의 모바일 브라우저에서는 요소를 탭한 후 hover 및 hover 상태가 요소에 적용됩니다.
어떤 스타일을 설정할지, 사용자가 스타일을 터치한 후 어떻게 보일지 신중하게 고려하세요.
기본 브라우저 스타일 숨기기
여러 상태의 스타일을 추가하면 대부분의 브라우저에서 사용자 터치에 반응하여 자체 스타일을 구현합니다. 이는 휴대기기가 처음 실행될 때 여러 사이트에 :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;
}
맞춤 동작 구현
사이트의 맞춤 상호작용과 동작에 관한 아이디어가 있다면 다음 두 가지 주제를 염두에 두어야 합니다.
- 모든 브라우저를 지원하는 방법
- 프레임 속도를 높게 유지하는 방법
이 도움말에서는 모든 브라우저를 지원하는 데 필요한 API와 이러한 이벤트를 효율적으로 사용하는 방법을 알아봅니다.
동작으로 실행하려는 작업에 따라 사용자가 한 번에 하나의 요소와 상호작용하도록 할 수도 있고 또는 동시에 여러 요소와 상호작용할 수 있도록 할 수도 있습니다.
이 도움말의 두 가지 예, 즉 모든 브라우저를 지원하는 방법과 프레임 속도를 높게 유지하는 방법을 살펴보겠습니다.
첫 번째 예를 사용하면 사용자가 하나의 요소와 상호작용할 수 있습니다. 이 경우 동작이 처음에 요소 자체에서 시작되었다면 모든 터치 이벤트가 이 요소에 제공되도록 할 수 있습니다. 예를 들어 스와이프 가능 요소에서 손가락을 이동해도 여전히 요소를 제어할 수 있습니다.
이는 사용자에게 상당한 유연성을 제공하지만 사용자가 UI와 상호작용할 수 있는 방식을 제한합니다.
그러나 사용자가 멀티 터치를 사용하여 동시에 여러 요소와 상호작용해야 한다면 터치를 특정 요소로 제한해야 합니다.
이는 사용자에게 더 유연하지만 UI 조작을 위한 로직이 복잡하고 사용자 오류에 대한 복원력이 떨어집니다.
이벤트 리스너 추가
Chrome (버전 55 이상), Internet Explorer, Edge에서는 PointerEvents
를 사용하여 맞춤 동작을 구현하는 것이 좋습니다.
다른 브라우저에서는 TouchEvents
및 MouseEvents
가 올바른 방법입니다.
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
를 호출한 후 터치가 발생한 위치와 관계없이 이벤트를 추적합니다.
마우스 이동 및 종료 이벤트의 경우 동작 시작 메서드 내에 이벤트 리스너를 추가하고 이 리스너를 문서에 추가합니다. 즉, 동작이 완료될 때까지 커서를 추적할 수 있습니다.
이를 구현하는 단계는 다음과 같습니다.
- 모든 TouchEvent 및 PointerEvent 리스너를 추가합니다. MouseEvents의 경우 시작 이벤트만 추가합니다.
- 시작 동작 콜백 내에서 마우스 이동 및 종료 이벤트를 문서에 바인딩합니다. 이렇게 하면 이벤트가 원래 요소에서 발생하는지와 관계없이 모든 마우스 이벤트가 수신됩니다. PointerEvents의 경우 추가 이벤트를 모두 수신하려면 원래 요소에서
setPointerCapture()
를 호출해야 합니다. 그런 다음 동작의 시작을 처리합니다. - 이동 이벤트를 처리합니다.
- 종료 이벤트에서 문서에서 마우스 이동 및 종료 리스너를 삭제하고 동작을 종료합니다.
다음은 이동 및 종료 이벤트를 문서에 추가하는 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);
문서에 이동 이벤트를 추가하는 이 패턴을 따르면 사용자가 요소와 상호작용을 시작하고 동작을 요소 외부로 이동하면 이벤트가 문서로부터 수신되므로 페이지의 위치에 관계없이 마우스 움직임이 계속 발생합니다.
이 다이어그램은 동작이 시작되면 문서에 이동 및 종료 이벤트를 추가할 때 터치 이벤트가 어떻게 작동하는지 보여줍니다.
효율적인 터치 반응
이제 시작 및 종료 이벤트를 처리했으므로 실제로 터치 이벤트에 응답할 수 있습니다.
모든 시작 및 이동 이벤트에 대해 이벤트에서 x
및 y
를 쉽게 추출할 수 있습니다.
다음 예에서는 targetTouches
가 있는지 확인하여 이벤트가 TouchEvent
에서 발생했는지 확인합니다. 포함하는 경우 첫 번째 터치에서 clientX
및 clientY
를 추출합니다.
이벤트가 PointerEvent
또는 MouseEvent
인 경우 이벤트 자체에서 직접 clientX
및 clientY
를 추출합니다.
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()
에 익숙하지 않은 경우 여기에서 자세히 알아보세요.
일반적인 구현은 시작 및 이동 이벤트에서 x
및 y
좌표를 저장하고 이동 이벤트 콜백 내에서 애니메이션 프레임을 요청하는 것입니다.
이 데모에서는 초기 터치 위치를 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()
콜백이 실행되면 rafPending
를 false
로 업데이트하기 전에 이동하려는 요소에 변환을 설정하여 다음 터치 이벤트에서 새 애니메이션 프레임을 요청할 수 있습니다.
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-action
를 manipulation
로 설정하면 기본적인 두 번 탭 동작이 방지됩니다.
이렇게 하면 두 번 탭 동작을 직접 구현할 수 있습니다.
다음은 일반적으로 사용되는 touch-action
값의 목록입니다.
이전 버전의 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의 업데이트 도움말을 참고하세요.
참조
터치 상태를 위한 유사 클래스
터치 이벤트에 대한 자세한 참고 자료는 W3C 터치 이벤트에서 확인할 수 있습니다.
터치, 마우스 및 포인터 이벤트
다음 이벤트는 애플리케이션에 새 동작을 추가하기 위한 기본 요소입니다.
터치 목록
각 터치 이벤트에는 세 개의 목록 속성이 포함됩니다.
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);
}
}
};