Hello World

모바일웹 터치 제스쳐 적용기 - [1] 터치 제스쳐 이해하기 본문

Javascript/Core

모바일웹 터치 제스쳐 적용기 - [1] 터치 제스쳐 이해하기

EnterKey 2016. 4. 18. 11:24
반응형

FE(Front-End) 개발자에게 모바일웹 서비스 개발은 매우 까다로운 작업입니다.


PC웹 환경은 디바이스 하드웨어 성능도 뛰어나며네트워크 속도도 기가(GIGA?) 막히게 빠릅니다(물론 아직도 저사양 PCIE7,8,9을 사용하는 분들이 소수 존재하기에, 구버전 웹브라우저에서 사용가능한 서비스들도 있습니다. 다만 화면깨짐은 있더라도 말이죠.)


반면, 모바일은 저성능의 디바이스, 일정하지 않은 네트워크 속도작은 디스플레이 사이즈라는 제약 안에서 개발이 진행됩니다거기다 OS 종류버전제조사에 따라 웹브라우저가 조금씩 다른 입출력 결과를 내놓고 있어모든 모바일 단말기에서 안정성을 보장하기란 쉽지 않습니다


이런 열악한 환경을 개선하기 위한 방법으로 리소스(이미지,JS,CSS) 사이즈를 최소화하고작은 화면에서도 사용자의 불편함이 없도록 화면설계(UI/UX)를 하는데오늘은 이 중에서 터치 제스쳐(gesture)에 대해 설명 드리고자 합니다. 






터치 제스쳐란?
PC웹에서 마우스(mouse)이벤트가 있다면 모바일웹에서 터치(touch)이벤트가 있습니다.
PC에서도 더블클릭우클릭 등 다양한 마우스이벤트를 지원하지만실제로 사용하는 서비스는 드뭅니다. PC웹 환경은 컨텐츠와 메뉴 버튼들을 한 화면에 담을 수 있고클릭만으로도 불편함 없이 사용이 가능합니다. 만약 클릭 이외의 다른 액션을 추가한다면사용자에게 사용법에 대한 가이드도 제공해야 하는데, 대부분의 사용자들은 가이드를 번거롭다고 느끼는 경향이 있습니다. 


반면 작은 화면큰 포인터(손가락환경인 모바일에서는 컨텐츠와 메뉴(네비게이션)를 한화면에 담기 어렵습니다컨텐츠 관련 메뉴 보기를 위한 추가적인 터치가 필요한데터치의 형태를 다양화하여 PC에서의 단축키와 같이 단축 액션을 제공하는 것이 터치 제스쳐 입니다.


대표적인 터치 제스쳐와실행되는 액션은 아래와 같습니다. 


출처 gesturecons.com




[주요 터치 제스쳐]

제스쳐 

주요 

 Tap

선택 

 Double Tap

열기(팝업), 화면 확대/원복 

 Hold(Long Tap)

모드 전환(리스팅<->액션)

 Drag-Flick(Swipe)

이전/다음 컨텐츠로 이동(삭제) 

 Pinch 

이미지 확대/축소 

 Rotation

이미지 회전 




현재 티몬 모바일웹에는 사용 중인 터치 제스쳐가 없습니다

만약 터치 제스쳐를 아래와 같이 적용한다면사용자의 편의성을 높이면서 딜 상세페이지에서 소비될 데이터를 아껴주는 효과를 가져오지 않을까요? 

  1. 상품목록 페이지 더블탭을 적용 - 새창에서 딜상세 페이지를 보여줌
  2. 상품목록 페이지 롱탭 - 상품 찜하기 또는 카트에 담기 액션과 연결
  3. 상품상세 페이지 플리킹(스와이프) - 이전/다음 딜로 이동
  4. 상품상세 페이지 더블탭 시 상품 이미지 확대/축소 (확대,축소 버튼제거)로 적용 - 플로팅 버튼(+)을 제거


추가적으로 프로모션 페이지에서는 쿠폰 다운로드 버튼 터치시 강도에 따라 쿠폰금액을 랜덤하게 발급하는 이벤트를 오픈할수도 있을 것입니다(※ IOS9 - IPHONE 6S - Safari browser 에서 터치의 강도를 측정할 수 있음)
 

적용분야를 알아봤으니코드 구현은 어떻게 해야하는지 알아보겠습니다. 

 





터치 이벤트
모바일 웹브라우저에서는 W3C에서 권고하는 4가지 터치 이벤트를 지원합니다. 


[기본 터치 이벤트]

이벤트 

설명

 touchstart

스크린에 손가락이 닿을 때 발생

 touchmove

스크린에 손가락이 닿은 채로 움직일 때 발생 

 touchend

스크린에서 손가락을 뗄 때 발생

 touchcancel

시스템에서 이벤트를 취소시킬 때 발생 (브라우저마다 다르기에, touched 이벤트로 간주해도 무방함)




각 이벤트 발생시에 전달되는 이벤트 객체에는 touches, targetTouches, changedTouches 의 속성이 존재하고,


[이벤트 객체 주요 속성]

 속성

설명

 changedTouches

이벤트에 할당된 모든 접촉점에 대한 터치 리스트

- touchstart 이벤트의 changedTouches는 현재 이벤트와 함께 바로 활성화된 터치점(touch point)의 리스트
- touchmove 이벤트의 changedTouches는 마지막 이벤트에서 이동한 터치점의 리스트
- touchend touchcancel 이벤트의 changedTouches는 화면에서 막 떼어진 터치점의 리스트

 targetTouches 

현재 이벤트의 타겟인 엘리먼트에서 시작되어 화면을 터치하고 있는 모든 접촉점에 대한 터치 리스트 

 touches

현재 화면을 터치하고 있는 모든 접촉점의 터치 리스트이며 터치된 구역의 정보를 담은 배열

- event.touches.length를 이용해 멀티터치 여부를 계산할 수 있음
- 특정 터치의 좌표 : event.touches[i].pageX(pageY)




각 속성들은 Touch 객체 배열로 구성되어 있습니다. 


[Touch 객체 속성]

 속성

설명

 identifier

인식 점을 구분하기 위한 인식 점 번호 

 screenX

디바이스 화면을 기준으로 한 X좌표 

 screenY

바이스 화면을 기준으로 한 좌표 

 clientX

 브라우저 화면을 기준으로 한 좌표 (스크롤 미포함)

 clientY

 브라우저 화면을 기준으로 한 좌표 (스크롤 미포함)

 pageX

 가로 스크롤을 포함한 브라우저 화면을 기준으로 한 좌표

 pageY

 세로 스크롤을 포함한 브라우저 화면을 기준으로 한 좌표

 target

 터치된 DOM 객체




참고로, IOS의 경우 멀티터치에 대한 비표준 이벤트를 추가로 제공하여편의성을 높이고 있습니다.


[IOS 제스쳐 이벤트]

 이벤트 명

설명

 gesturestart

두번째 손락락이 화면에 닿을 때 발생 

 gesturechange 

두개 이상의 손가락이 화면에 닿은 상태에서 움직일때 발생 

 gestureend

두개 이상의 손가락이 화면에 닿은 상태에서 하나의 손가락이 떨어질 때 발생 





터치 제스쳐 구현

그럼 Tap, Double Tap을 간략하게 구현해 보겠습니다.
 

Tap

(Tap)은 touchstart 이벤트 발생후 touchemove 이벤트 발생없이 touchend이벤트가 발생할때 탭으로 식별합니다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var bStartEvent = false;
//touchstart 이벤트 발생 여부 플래그
var bMoveEvent = false;
//touchmove 이벤트 발생 여부 플래그
 
function init(){
    document.addEventListener("touchstart", this.onStart.bind(this), false);
    document.addEventListener("touchmove", this.onMove.bind(this), false);
    document.addEventListener("touchend", this.onEnd.bind(this), false);
}
 
function onStart(e) {
    bStartEvent = true;
}
 
function onMove(e) {
    
    if(!bStartEvent) {
        return;
        //touchstart 이벤트가 발생하지 않으면 처리하지 않는다.
    }
    bMoveEvent = true;
    //touchMove 이벤트 발생 여부를 설정한다.
}
 
function onEnd(e) {
    if(bStartEvent && !bMoveEvent) {
        //클릭 이벤트로 판단한다.
        alert('Tap!');
    }
    //각 플래그 값을 초기값으로 설정한다.
    bStartEvent = false;
    bMoveEvent = false;
}






Double Tap

더블탭 구현은 탭구현에서 탭과 다음탭이벤트 사이의 간격체크첫번째 탭과 두번째 탭 사이의 포인트거리체크 로직이 추가됩니다.
탭과 다음탭 사이의 간격이 200ms 보다 짧으면 더블탭으로 판단하고첫번째 탭이후 300ms 후 더블탭이벤트가 발생하지 않았다면 탭이벤트로 식별합니다(※ 탭만 사용할경우에 비해 탭이벤트가 300ms 늦게 발생합니다.) 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
var bStartEvent = false//touchstart 이벤트 발생 여부 플래그
var bMoveEvent = false//touchmove 이벤트 발생 여부 플래그
 
htClickInfo = { //더블탭을 판단하기 위한 마지막 탭 이벤트의 정보 해시 테이블
    sType : null,
    nX : -1,
    nY : -1,
    nTime : 0
}
 
var nDoubleTapDuration = 200//더블탭을 판단하는 기준 시간(ms)
var nTapThreshold = 5//탭을 판단하는 거리
var oTapEventTimer = null//탭-더블탭 대기 타이머
 
function init(){
    document.addEventListener("touchstart", this.onStart.bind(this), false);
    document.addEventListener("touchmove", this.onMove.bind(this), false);
    document.addEventListener("touchend", this.onEnd.bind(this), false);
}
 
function initClearInfo() {
    htClickInfo.sType = null;
}
 
function onStart(e) {
    bStartEvent = true;
}
 
function onMove(e) {
    if(!bStartEvent) {
        return//touchstart 이벤트가 발생하지 않으면 처리하지 않는다.
    }
    bMoveEvent = true//touchmove 이벤트 발생 여부를 설정한다.
}
 
function onEnd(e) {
    var nX = e.changedTouches[0].pageX;
    var nY = e.changedTouches[0].pageY;
    var nTime = e.timeStamp;
    
    if(bStartEvent && !bMoveEvent) {
        //이전 탭 이벤트와 시간 차이가 200ms 이하일 경우
        if(htClickInfo.sType == "tap" && (nTime - htClickInfo.nTime) <= nDoubleTapDuration){
            if( (Math.abs(htClickInfo.nX-nX) <= nTapThreshold)
&& (Math.abs(htClickInfo.nY-nY) <= nTapThreshold) ){
                //더블탭으로 판단한다. (탭이 발생하지 않게 탭 발생 타이머 초기화한다.)
                clearTimeout(oTapEventTimer);
                alert("Double Tap");
            }
        } else {
            //탭 이벤트로 판단한다.
            //현재 탭 이벤트들에 대한 정보를 업데이트한다.
            oTapEventTimer = setTimeout(function(){
                alert("Tap");
            }.bind(this), 300);
            htClickInfo.sType = "tap";
            htClickInfo.nX = nX;
            htClickInfo.nY =nY;
            htClickInfo.nTime = nTime;
        }
    } else {
        //탭 이벤트가 아니므로 탭 이벤트 정보를 초기화한다.
        initClearInfo();
    }
    
    //각 플래그 값을 초기값으로 세팅한다.
    bStartEvent = false;
    bMoveEvent = false;
}





Swipe

swipe는 touchstart이벤트와 touchEnd사이의 이동거리와각도방향을 고려해야 합니다.
각도 임계치를 정하여상하 스크롤시에 스와프이벤트가 발생하는 것을 방지해야 합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function onMove(e) {
    if(!bStartEvent) {
        return
    }
    
    var nX = e.changedTouches[0].pageX;
    var nY = e.changedTouches[0].pageY;
    
    //현재 touchmMove에서 사용자 터치에 대한 움직임을 판단한다.
    nMoveType = getMoveType(nX, nY);
    
    //현재 사용자 움직임을 수직으로 판단해 기본 브라우저의 스크롤 기능을 막고 싶으면 아래 코드를 사용한다.
    if(nMoveType === 1) {
        e.preventDefault();
    }
}
 
function getMoveType(x, y) {
    //0은 수평방향, 1은 수직방향
    var nMoveType = -1;
    var nX = Math.abs(htTouchInfo.nX- x);
    var nY = Math.abs(htTouchInfo.nY - y);
    var nDis = nX + nY;
    
    //현재 움직인 거리가 기준 거리보다 작을 땐 방향을 판단하지 않는다.
    if(nDis < 25) { return nMoveType }
    
    //수평 방향을 판단하는 기준 기울기
    var nHSlope = ((window.innerHeight / 2/ window.innerWidth).toFixed(2* 1;
    
    //현재 터치의 기울기
    var nSlope = parseFloat((nY / nX).toFixed(2), 10);
    
    if(nSlope > nHSlope) {
        nMoveType = 1;
    } else {
        nMoveType = 0;
    }
    return nMoveType;
}






마치며

한 손가락의 터치 제스쳐인 tap, double tap, swipe 구현도 쉽지 않습니다정확한 제스쳐 식별을 위해서는 적절한 임계치(터치 시간각도속도 등정보가 필요할 것입니다두 손가락의 제츠쳐인 Pinch, Rotation은 두 포인트 사이의 거리각도가까워지는지멀어지는지좌측으로 회전하는지우측으로 회전하는지 등 식별이 훨씬 복잡해집니다


다음 2부에서는 이런 어려움을 해결시켜줄 터치이벤트 라이브러리 Hammer.js를 소개하고실제 서비스에 적용 가능한 코드를 구현해 보겠습니다.
 

참고
http://gesturecons.com
http://d2.naver.com/helloworld/80243
http://hcitrends.kr/portfolio-item/ui-report
http://www.w3.org/TR/touch-events/

 


출처: http://tmondev.blog.me/220680473335

반응형
Comments