모바일 웹 환경에서 pinch-zoom 구현하기 (2) - MouseEvent와 TouchEvent

기운찬곰·2023년 5월 10일
1
post-thumbnail

💻 카카오엔터테이먼트 FE 블로그를 참조하여 실습 및 정리한 내용입니다.

🧑🏻‍💻 코드 참고(아직 개발 중) : https://github.com/ckstn0777/pinch-zoom-practice


Overview

이번 시간에는 간단하게 TouchEvent에 대해 알아보도록 하며 MouseEvent와는 어떤 차이가 있는지 살펴보겠습니다.


MouseEvent와 TouchEvent

일단 프로젝트를 하기 전에 터치 이벤트에 대해 알 필요가 있었습니다. 근데 그러고 보니 마우스 이벤트도 있는데... 이 둘의 차이는 뭘까? 궁금했습니다.

당연히 마우스 이벤트는 마우스를 사용했을 때 발생하는 이벤트일 것이고, 터치 이벤트는 모바일 포함 터치 스크린에서 터치했을 때 발생하는 이벤트이긴 할 것입니다. 하지만... 여태껏 터치 이벤트 리스너를 구현하지 않아도 모바일에서 터치하면 클릭도 되고, 별로 불편한 점은 없었던 거 같습니다. 🤔

MouseEvent

MDN 참고 : https://developer.mozilla.org/ko/docs/Web/API/MouseEvent

마우스 이벤트에는 click, mouseup, mousedown 등 마우스를 사용하는 이벤트가 포함되어있습니다. 근데 이게 약간씩 차이가 있다보니 한번 알아봤습니다.

참고 : https://hianna.tistory.com/492 (마우스 이벤트 종류)

이벤트 명설명
click버튼을 눌렀다가 떼었을 때 발생
dblclick더블 클릭했을 때 (이건 처음봤는데)
mousemoveelement 안에서 마우스를 움직일때 발생
mousedown버튼을 눌렀을때 발생
mouseup버튼을 눌렀다가 떼었을 때 발생. 단, click 보다 먼저 발생한다.
mouseover(↔ mouseout)바깥에서 안으로 들어올 때 (↔ 안에서 바깥으로 나갈때)
mouseenter(↔ mouseleave)바깥에서 안으로 들어올 때 (↔ 안에서 바깥으로 나갈때). 단, 버블링이 발생하지 않는다.
contextmenu마우스 오른쪽 버튼을 눌렀을 때 발생

알게 모르게 헷갈리는 내용을 정리해보겠습니다.

  • click 이벤트는 mousedown, mouseup 이벤트가 순서대로 발생한 후 발생합니다.
  • mouseup과 click 이벤트는 좀 다르게 동작합니다. 타겟 요소 밖에서 마우스를 누르고, 마우스를 움직여서 타겟 요소 안으로 들어와서 마우스를 떼면 mouseup 이벤트만 발생합니다. (!!)
  • mousemove는 element 안에서 마우스를 움직였을 때 발생합니다. 근데 마우스가 1픽셀이라도 움직이면 발생하기 때문에 이 이벤트를 그냥 사용하면 성능상 안좋을 수 있습니다.
  • mouseover(↔ mouseout)와 mouseenter(↔ mouseleave)의 차이는 이벤트 버블링 효과가 발생하는게 다릅니다. mouseover(↔ mouseout)는 이벤트 버블링이 발생하고, mouseenter(↔ mouseleave)는 발생하지 않습니다.

TouchEvent

MDN 참고 : https://developer.mozilla.org/en-US/docs/Web/API/Touch_events

터치 이벤트에는 4가지 종류가 있습니다.

이벤트 명설명
touchstart터치가 시작되는 순간 발생 (mousedown과 비슷합니다)
touchmove터치한 상태에서 움직이면 발생 (mousemove과 비슷합니다)
touchend터치가 끝났을 때 발생 (mouseup 혹은 click이랑 비슷합니다)
touchcancel터치가 취소되면 발생

그리고 터치에 대한 정보(터치 좌표값 등)를 알아내는 방법은 프로퍼티 중에 touches, targetTouches, changedTouches 를 확인해보면 됩니다. 또한, 멀티 터치를 한 경우 TouchList라는 유사배열객체에 정보가 추가됩니다.

근데 저 3개의 프로퍼티 차이가 뭔지 궁금해졌습니다. 그래서 좀 찾아봤습니다.

참고 : https://stackoverflow.com/questions/7056026/variation-of-e-touches-e-targettouches-and-e-changedtouches

  • changedTouches: A list of information for every finger involved in the event. (이벤트를 유발시킨 모든 손가락에 대한 정보)
  • touches : touches : A list of information for every finger currently touching the screen. (현재 화면을 터치하는 모든 손가락에 대한 정보)
  • targetTouches : Like touches, but is filtered to only the information for finger touches that started out within the same node. (touches와 유사하지만, 동일한 노드 내에서 시작된 손가락 터치에 대한 정보로만 필터링됩니다)
// 터치 시작
function touchStartHandler({ event }: { event: TouchEvent }) {
  console.log("touchStartHandler");
  console.log("touches", event.touches);
  console.log("targetTouches", event.targetTouches);
  console.log("chnagedTouches", event.changedTouches);
}

// 터치 이동
function touchMoveHandler({ event }: { event: TouchEvent }) {
  console.log("touchMoveHandler");
  console.log("touches", event.touches);
  console.log("targetTouches", event.targetTouches);
  console.log("chnagedTouches", event.changedTouches);
}

// 터치 이벤트 리스너 등록 
screen.addEventListener("touchstart", (event) => touchStartHandler({ event }));
screen.addEventListener("touchmove", (event) => touchMoveHandler({ event }));

각각에 대한 상황을 보면서 어떤 부분이 다른지 살펴보겠습니다.

  • 제가 손가락을 내려놓으면 3개 모두 같은 정보를 가집니다.

  • 제가 두번째 손가락을 내려놓을때, touches는 2개의 정보를 갖습니다. targetTouches는 손가락이 첫 번째 손가락과 동일한 노드에 배치된 경우에만 두 개의 항목을 가집니다. changedTouches는 두번째 손가락과 관련된 정보만을 가집니다. 그것이 이벤트를 유발했기 때문입니다. 하지만, 두개를 동시에 내려놓으면 결과는 달라질 수 있습니다.

  • 손가락을 움직이면, 오직 changedTocuhes만 변경됩니다. 이동하는 손가락 수(최소 하나)와 관련된 정보가 포함됩니다. -> 근데 확인해보면 3개 다 바뀌는거 같긴 합니다. 근데 여기서 중요한건 changedTocuhes가 동적인 느낌이고, touches랑 targetTouches은 정적인 느낌인거 같습니다. 그래서 결과가 같을수도 있고 다를 수도 있습니다.

  • 제 마지막 손가락을 제거하면, touches와 targetTouches는 비어있고, changedTouches에는 마지막 손가락에 대한 정보가 포함됩니다. (아… 이제 어떤 느낌인지는 알겠습니다)

✍️ 정리해보면 changedTocuhes는 이벤트를 유발시킨 동적인 느낌이 강하게 듭니다. 반면, touches와 targetTouches는 정적인 느낌입니다. 진짜 그냥 있는 그대로의 사실 정보를 보여줍니다. 현재 몇 개가 터치 되어있는지... 그래서 마지막 실험 'touchend'에서 그 차이를 명확히 알 수 있었습니다.


MouseEvent vs. TouchEvent

그러면 우리는 마우스랑 터치를 지원하려면 둘 다 사용해야 하는 걸까요? 라는 의문이 생겼습니다.

  • Q1. 마우스 이벤트만 사용하면 스마트폰에서 터치가 안될까요? 근데 그건 아닌거 같습니다. click 이벤트 사용하면 마우스나 터치 스크린에서 정상적으로 동작하는거 같아보입니다.
  • Q2. 마우스 이벤트나 터치 이벤트 둘 다 사용해야 할까요? 아니면 하나만 사용해도 될까요?

참고 : https://ui.toast.com/posts/ko_20220106

일단, 저 의문을 해결하려면 결국 여러 상황을 가지고 테스트를 해봐야 될 거 같습니다. 근데 toast 개발 블로그에서 이미 진행한 실험이 있더군요. 이를 참고해서 보면 다음과 같습니다.

function createParagraph(text) {
  const el = document.createElement('p');
  el.innerText = text;

  return el;
}

const printEl = document.getElementById('print');

['touchstart', 'touchmove', 'touchend', 'mousedown', 'mousemove', 'mouseup', 'click'].forEach(
  (eventType) => {
    document.addEventListener(eventType, () => {
      printEl.appendChild(createParagraph(eventType));

      // 스크롤을 최하단으로 이동시켜준다.
      window.scrollTo(0, document.body.scrollHeight);
    });
  }
);

  • mouse click : 마우스 클릭했을 때는 터치 이벤트가 발생하지 않습니다. 예상대로 동작합니다.
  • touch : 근데 터치를 했을 경우는 좀 다릅니다. 터치 이벤트가 발생한 후 마우스 이벤트가 발생하고 최종적으로 click이 됩니다. 진행과정에 있어서 속도차이는 그렇게까지는 없는거 같습니다. 못느낄 정도. 아무튼 이런 이유로 필요에 따라. e.preventDefault 를 사용하기도 합니다.
  • touch and move : 특이하게도 여기서는 마우스 이벤트가 발생하지 않습니다.
  • multi touch : 이것도 좀 예상 외로 마우스 이벤트는 발생하지 않습니다. 다만, 약간 터치를 잘못해도 마우스 이벤트가 발생할 수 있습니다. (아 그니까 뭔가 빠르게 해야 될 거 같네. 아닌가. 쓰읍...)
  • double tap : 음? 이건 빠르게 두번 연속 터치하는거 아닌가? 근데 아무리 빨리해도 마우스 이벤트도 같이 발생하는데?

✍️ 여기서 중요한 사실은 터치를 할 때 마우스 이벤트도 같이 발생한다는 것이다. 그래서... 여태까지 잘 신경을 안쓰고 있었나보다.

그래서 toast 개발 블로그에서는 아예 모바일 환경이면 마우스 이벤트를 터치 이벤트로 바꿔버리는거 같습니다.

“터치와 클릭은 화면과 상호작용을 위해 화면상의 요소를 눌렀다 뗀다는 단순하지만 아주 중요한 공통점을 가지고 있다.” 그렇기 때문에 아래처럼 이벤트 타입을 변환하는 것만으로 터치와 클릭을 바꿔서 지원할 수 있는 것이다.

function getConvertedEventType(type) {
  if (isMobile()) {
    if (type === 'mousedown') {
      type = 'touchstart';
    } else if (type === 'click') {
      type = 'touchend';
    }
  }

  return type;
}

이렇게 사용한 이유에 대해서는 다음과 같이 설명하고 있었습니다.

  1. 마우스는 항상 위에 떠 있고, 터치는 그렇지 않다. 한 화면 내에 떨어져 있는 두 요소를 클릭 또는 터치하는 경우를 생각해보자. 마우스는 왔다 갔다 하면서 클릭을 해야 한다. 반면 터치는 손가락으로 둘다 동시에 터치할 수도 있고, 번갈아가며 할 수도 있다. 화면은 두 터치 행위를 이어주는 동작을 알 수 없다.
  2. 클릭은 단 하나의 포인터를 이용해 상호작용하지만, 터치는 2개 이상의 터치 포인터로 상호작용할 수 있다. 이로 인해 모바일 장치를 이용할 때는 줌-인, 아웃, 회전 등의 멀티 터치 제스쳐를 사용할 수 있다. (중요!!)
  3. 터치는 누르는 동작, 이동하는 동작, 떼는 동작만을 이용해 모든 동작을 수행해야 한다. 여기서 발생하는 가장 큰 차이점이 누르고 움직인 후 떼는 동작이다. 마우스의 경우 이 동작을 통해 일반적으로 드래그 앤 드롭을 수행할 수 있지만, 터치는 스크롤을 수행할 수 있다.

✍️ 마우스랑 터치는 '눌렀다 뗀다' 라는 공통점은 가지고 있지만, 터치만을 위한 세부 제스처를 지원하기 위해서는 각각 구현이 필요하겠구나 라는 생각이 든다. 프론트엔드 개발자로서 많은 생각을 들게 하는군...

참고 : https://stackoverflow.com/questions/14530734/handle-both-mouse-and-touch-events-on-touch-screens
(스택오버플로우에서도 "터치 이벤트와 마우스 이벤트를 모두 만들어야 하지만, 처리하는 터치 이벤트에 대해 preventDefault()를 호출하여 마우스 이벤트가 실행되지 않도록 해야 합니다." 라고 합니다)

document.addEventListener('click', () => {
  printEl.appendChild(createParagraph('click'));
});

document.addEventListener('touchend', (ev) => {
  ev.preventDefault(); // touchend 일 경우 이후 마우스 이벤트 발생을 막기
  printEl.appendChild(createParagraph('touch'));
});

마치면서

이번 시간에는 마우스 이벤트와 터치 이벤트를 보다 자세히 알게 되어서 의미 있었던거 같습니다. 모바일 웹 (webview)를 만드는 과정은 확실히 조금 까다롭고 전문적인 지식이 많이 필요할 거 같습니다. 하지만 이러한 기술들이 프론트엔드 개발자로서 한층 더 성장할 수 있는 밑거름이 될 수 있을 거 같다는 생각이 듭니다.

이것을 보면서 다양한 터치 이벤트를 구현해볼 수 있지 않을까 라는 기대감도 듭니다. 터치 드래그도 있을 수 있고, 터치 슬라이드도 만들 수 있겠네요. 참고 : https://velog.io/@bepyan/Drag-이벤트-뽀개기 (되게 재밌어 보이는게 많네요.ㅎㅎ. 저도 만들어보고 싶군요. )


참고 자료

profile
배움을 좋아합니다. 새로운 것을 좋아합니다.

0개의 댓글