터치 이벤트를 사용하여 다양한 제스처 분석
터치 이벤트
모바일 브라우저에서는 터치 이벤트를 지원한다. 터치 이벤트는 화면에 손가락을 터치했을 때 발생하는 이벤트로 다음 표와 같은 종류가 있다.
이벤트 | 설명 |
---|---|
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(); //터치 정보를 초기화한다.
}