updated_at: 2024-12-15 06:33

터치 이벤트를 사용하여 다양한 제스처 분석

터치 이벤트

모바일 브라우저에서는 터치 이벤트를 지원한다. 터치 이벤트는 화면에 손가락을 터치했을 때 발생하는 이벤트로 다음 표와 같은 종류가 있다.

터치 이벤트의 종류
이벤트 설명
touchstart 스크린에 손가락이 닿을 때 발생한다
touchend 스크린에서 손가락을 뗄 때 발생한다
touchcancel 시스템에서 이벤트를 취소시킬 때 발생한다.(브라우저 마다 다르게 발생)
touchmove 스크린에 손가락이 닿은 채로 움직일 때 발생한다

사용자 제스처 분석

  • 더블탭
  • 롱탭
  • 드래그

먼저 아래와 같이 이벤트 리스너를 등록하자.

var el = document.getElementsByTagName("canvas")[0];
el.addEventListener("touchstart", handleStart, false);
el.addEventListener("touchend", handleEnd, false);
el.addEventListener("touchcancel", handleCancel, false);
el.addEventListener("touchmove", handleMove, false);

touchmove 이벤트가 발생하지 않고 touchstart 이벤트, touchend 이벤트의 순서로 이벤트가 발생할 때 탭이라고 판단한다.
즉, touchstart -> touchend 이면 tap 이벤트 발생

var touchstartEvent = false;  
var touchmoveEvent = false;  

function handleStart(e) {  
  touchstartEvent = true;
}

function handleMove(e) {  
  if(!touchstartEvent) {
    return;
  }
  touchmoveEvent = true;
}

function handleEnd(e) {  
  if(touchstartEvent && !touchmoveEvent) {
    console.log('tap fired');  
  }

  touchstartEvent = false;
  touchmoveEvent = false;
}

더블탭

탭 이벤트가 두 번 발생하면 더블탭으로 판단한다.
touchstart -> touchend -> 약간의 시간경과(여기서는 200ms) -> touchstart -> touchend 이면 double tap 이벤트 발생


var touchstartEvent = false;
var touchmoveEvent = false;
var htClickInfo = { // 더블탭을 판단하기 위한 마지막 탭 이벤트의 정보 해시 테이블  
  sType : null,
  nX : -1,
  nY : -1,
  nTime : 0
}

var nDoubleTapDuration = 200; // 더블탭을 판단하는 기준 시간(ms)  
var nTapThreshold = 5; // 탭을 판단하는 거리 (move와 차이를 두기 위해)

function initClearInfo() {  
  htClickInfo.sType = null;
}

function handleStart(e) {  
  touchstartEvent = true;
}

function handleMove(e) {  
  if(!touchstartEvent) {
    return;
  }
  touchmoveEvent = true;
}

function handleEnd(e) {  
  var nX = e.$value().changedTouches[0].pageX;
  var nY = e.$value().changedTouches[0].pageY;
  var nTime = e.$value().timeStamp;

  if(touchstartEvent && !touchmoveEvent) {
    //이전 탭 이벤트와 시간 차이가 20ms 이하일 경우
    if(htClickInfo.sType == 'click' && (nTime – htClickInfo.timeStamp) <= nDoubleTapDuration){
      if( (Math.abs(htClickInfo.nX-nX) <= nTapThreshold) && (Math.abs(htClickInfo.nY-nY) <= nTapThreshold) ){
        //더블탭으로 판단한다.
      }
    } else {
      //탭 이벤트로 판단한다.
      //현재 탭 이벤트들에 대한 정보를 업데이트한다.
      htClickInfo.sType = 'click';
      htClickInfo.nX = nX;
      htClickInfo.nY =nY;
      htClickInfo.nTime = nTime;
    }   
  } else {
    //탭 이벤트가 아니므로 탭 이벤트 정보를 초기화한다.
    initClearInfo();
  }

  // reset
  touchstartEvent = false;
  touchmoveEvent = false; 
}

롱탭

touchstart 이벤트 발생 후, 기준 시간 내에 touchmove 이벤트와 touchend 이벤트가 발생하지 않으면 롱탭으로 판단한다. 이 예제에서는 1000ms로 설정했다.
touchstart -> 약간의 시간경과(여기서는 1000ms) 이면 long tap 이벤트 발생

function startLongTapTimer() { //롱탭을 판단하는 타이머를 실행하는 코드  
  var self = this;
  this.nLongTabTimer = setTimeout(function() {
      //롱탭 이벤트로 판단한다.
      alert('롱탭');
      delete self.nLongTabTimer;
  }, 1000)
}

function deleteLongTabTimer() {  
  //활성화된 롱탭 타이머를 삭제하는 코드
  if( typeof this.nLongTabTimer !== 'undefined') {
    clearTimeout(this.nLongTabTimer);
    delete this.nLongTabTimer;
  }
}

function handleStart(e) {  
  startLongTapTimer(); //롱탭을 판단하기 위한 타이머를 활성화
  touchstartEvent = true;
}

function handleMove(e) {  
  if(!touchstartEvent) {
    return
  }
  deleteLongTabTimer(); //touchmove 이벤트가 발생했기 때문에 롱탭 타이머 삭제
}

function handleEnd(e) {  
  if(!touchstartEvent) {
    return
  }
  deleteLongTabTimer(); //touchend 이벤트가 발생했기 때문에 롱탭 타이머 삭제
  touchstartEvent = false;
}

드래그

플리킹이나 스크롤 기능을 구현하려면 사용자가 터치해서 움직이는 방향이 수직 방향인지 수평 방향인지 판단해야 한다. 사용자 움직임의 방향을 판단하는 기준은 다음과 같이 여러 가지가 있다.

  • 수평 방향 기준 기울기 = (모바일 단말기 세로 너비/2) / 모바일 단말기 가로 너비
  • 수직 방향 기준 기울기 = 모바일 단말기 세로 너비 / (모바일 단말기 가로 너비/2)
  • 사용자가 기준 거리 이상 움직였을 때, 움직인 수직, 수평 방향의 거리를 이용해 현재 사용자 움직임 기울기를 구하고, 그 기울기가 수평 방향 기준 기울기보다 크면 수직 방향으로 판단하고, 작으면 수평 방향으로 판단한다. 기울기를 판단하는 기준 거리는 개발자가 설정할 수 있으며, 이 예제에서는 25픽셀로 설정했다.

가로 플리킹이나 스크롤에 적용할 때는 기울기를 판단하는 기준 거리를 4픽셀로 잡는 것이 좋다. 그 이상으로 값을 설정하면 기본 수직 스크롤이 발생하여 touchmove 이벤트나 touchend 이벤트가 발생하지 않을 수 있기 때문이다.

touchstart -> touchmove 이면 drag 이벤트 발생
touchstart -> touchmove -> 기울기 판단 -> 수직/수평 이동

var touchstartEvent = false; //touchstart 이벤트 발생 여부 플래그  
var nMoveType = -1; //현재 판단된 사용자 움직임의 방향  
var htTouchInfo = { //touchstart 시점의 좌표와 시간을 저장하기  
  nStartX : -1,
  nStartY : -1,
  nStartTime : 0
};
//수평 방향을 판단하는 기준 기울기
var nHSlope = ((window.innerHeight / 2) / window.innerWidth).toFixed(2) * 1;

function initTouchInfo() { //터치 정보들의 값을 초기화하는 함수  
  htTouchInfo.nStartX = -1;
  htTouchInfo.nStartY = -1;
  htTouchInfo.nStartTime = 0;
}

//touchstart 좌표값과 비교하여 현재 사용자의 움직임을 판단하는 함수
function getMoveType(x, y) {  
  //0은 수평방향, 1은 수직방향
  var nMoveType = -1;

  var nX = Math.abs(htTouchInfo.nStartX - x);
  var nY = Math.abs(htTouchInfo.nStartY - y);
  var nDis = nX + nY;
  //현재 움직인 거리가 기준 거리보다 작을 땐 방향을 판단하지 않는다.
  if(nDis < 25) { return nMoveType }

  var nSlope = parseFloat((nY / nX).toFixed(2), 10);

  if(nSlope > nHSlope) {
    nMoveType = 1;
  } else {
    nMoveType = 0;
  }

  return nMoveType;
}

function handleStart(e) {  
  initTouchInfo(); //터치 정보를 초기화한다.
  nMoveType = -1; //이전 터치에 대해 분석한 움직임의 방향도 초기화한다.
  //touchstart 이벤트 시점에 정보를 갱신한다.
  htTouchInfo.nStartX = e.$value().changedTouches[0].pageX;
  htTouchInfo.nStartY = e.$value().changedTouches[0].pageY;
  htTouchInfo.nStartTime = e.$value().timeStamp;
  touchstartEvent = true;
}

function handleMove(e) {  
  if(!touchstartEvent) {
    return
  }
  var nX = e.$value().changedTouches[0].pageX;
  var nY = e.$value().changedTouches[0].pageY;

  //현재 touchmMove에서 사용자 터치에 대한 움직임을 판단한다.
  nMoveType = getMoveType(nX, nY);

  //현재 사용자 움직임을 수직으로 판단해 기본 브라우저의 스크롤 기능을 막고 싶으면 아래 코드를 사용한다.
  if(nMoveType === 1) {
    e.stop(jindo.$Event.CANCLE_DEFAULT);
  }

}

function handleEnd(e) {  
  if(!touchstartEvent) {
    return
  }

  //touchmove에서 움직임을 판단하지 못했다면 touchend 이벤트에서 다시 판단한다.
  if(nMoveType < 0) {
    var nX = e.$value().changedTouches[0].pageX;
    var nY = e.$value().changedTouches[0].pageY;
    nMoveType = getMoveType(nX, nY);
  }
  touchstartEvent = false;
  nMoveType = -1; //분석한 움직임의 방향도 초기화한다.
  initTouchInfo(); //터치 정보를 초기화한다.
}
평점을 남겨주세요
평점 : 5.0
총 투표수 : 1

질문 및 답글