일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 백준구현
- 자바스크립트
- 안드로이드 스튜디오
- css기초
- CSS
- 백준nodejs
- JS
- 백준
- 백준골드
- 백준js
- 다이나믹프로그래밍
- 포이마웹
- 코테
- 백준구현문제
- 백준알고리즘
- HTML5
- 프로그래머스코테
- JS프로그래머스
- dp알고리즘
- js코테
- 알고리즘
- 코딩테스트
- 프로그래머스
- HTML
- 리액트커뮤니티
- 리액트
- 리액트댓글기능
- 프로그래머스JS
- 익스프레스
- 몽고DB
- Today
- Total
개발새발 로그
[2024-02-29] 팀 프로젝트 개인 회고 - Carousel 모바일 버그 해결, 마우스 이벤트와 터치 이벤트 순서 본문
저번에 Carousel을 완성하고 보니 Touch이벤트에서 문제가 생겼다.
모바일 환경에서 기능을 수행하니까 드래그는 되지만 그냥 클릭했을 때 Link기능이 안되는 것이었다.
이 문제의 원인을 찾고 해결하는 과정을 작성해보았다.
1. 아니! 왜 클릭이 안될까?
웹 환경에서는 정상적으로 클릭까지 작동했지만
모바일 환경에서는 드래그만 작동했다.
이를 통해서 등록된 이벤트와 이전에 생성했던 isDragging 상태가 문제가 있음을 추측했다.
위 사진에서는 onTouchMove와 onTouchEnd에 handleMouse~ 함수를 넣어줫는데 어차피 같은 기능을 하는 거여서 넣어준 것이다.
일단 아래와 같이 이벤트에 모든 console.log를 작성해서 문제를 찾아보았다.
첫번째 블록이 터치를 딱 했을 때 나오는 로그고
아래 블록이 터치를 뗐을 때 나오는 로그다.
이 로그를 보면서 이상한 부분이 두가지 있었다.
1. move이벤트 다음에 mouseDown이벤트에 등록된 함수가 실행되는 것이었다.
2. 분명 터치이벤트인데 왜 모바일인가? 에서 false가 나오는 것인지?
모바일을 검사하는 코드는 아래와 같다.
console.log( "touches" in e );
그런데 모바일 환경 터치 앤 드래그를 했을 때는 아래와 같았다.
먼저 모바일인가? 에 대해서는 true이다.
그리고 Carousel 기능도 정상작동한다.
마지막에는 isDragging과 isClick을 true출력하고 set함수로 모두 false 해주고 있다.
여기서 추측가능한 문제는 일단 터치 후 바로 터치를 떼는 동작이 터치 이벤트가 아닌 마우스 이벤트로 동작하고 있는 점이다.
일단 이벤트를 다시생각해보자
터치 후 바로 떼는 동작을 했을 경우 예상되는 이벤트 발생 순서는 아래와 같다.
touchStart -> touchEnd -> mouseMove -> mouseDown -> mouseUp
근데 touchEnd 이벤트는 위에서 발생하지 않았다.
왜인지 보니 아래와 같이 isDragging이 아닐 때는 반환하도록해서 그렇다.
즉, 터치 후 touchMove가 없었으니 isDragging이 false일 것이고, touchEnd는 당연히 실행이 안되는 것이다.
🚨여기서 잠깐!
Touch와 Mouse 이벤트 순서
이렇게 순서되는 이유는?
마우스 이벤트가 터치 이벤트 후에 추가로 발생하는 것은 주로 데스크톱과 모바일 환경 간의 호환성을 유지하기 위한 것이다.
모바일 기기에서의 터치 이벤트는 데스크톱 환경의 마우스 이벤트와 유사한 동작을 수행한다.
터치와 클릭, 터치 후 움직이는 것과 드래그가 유사하다.
초기에 웹 표준이 만들어졌을 때는 모바일 환경의 터치 이벤트를 고려하지 않았고, 대부분의 웹 사이트는 마우스 이벤트를 기반으로 작성되었었다고한다.
따라서, 모바일 환경이 등장한 후에도 기존의 웹 사이트가 정상적으로 작동하려면 터치 이벤트를 마우스 이벤트로 변환하는 것이 필요하다.
이런 이유로, 브라우저는 모바일 환경에서 터치 이벤트가 발생하면 해당 터치 이벤트와 유사한 마우스 이벤트를 추가로 발생시킨다.
이를 통해 터치 이벤트를 지원하지 않는 웹 사이트도 모바일 환경에서 정상적으로 작동할 수 있게 됩니다
이러한 호환성이 필요한 이유는 웹 기술이 빠르게 변화하는 만큼, 오래된 웹사이트나 레거시 코드가 최신 기술을 따라잡지 못하는 경우가 많기 때문이라고 한다.
이런 이유로 웹 표준에서는 이전 기술과의 호환성을 중요하게 생각하고 있습니다.
다시 돌아와서 아래 순서를 다시 보자
touchStart -> mouseMove -> mouseDown -> mouseUp
그럼 왜 Touch이벤트에서 Mouse 이벤트가 일어난지 알아냈다.
그러면 이 이벤트를 막아야하는가? 에 대해서 생각했다.
마우스 이벤트 발생으로 꼬여버린 isDragging 상태
내 이벤트 코드를 보자
const handleMouseDown = (e: React.MouseEvent) => {
...
console.log("isDragging : " + isDragging + " isClick : " + isClick);
console.log("모바일인가?");
console.log("touches" in e);
console.log("마우스 다운");
setIsClick(true);
};
const handleTouchDown = (e: React.TouchEvent) => {
...
console.log("isDragging : " + isDragging + " isClick : " + isClick);
console.log("모바일인가?");
console.log("touches" in e);
console.log("터치 다운");
setIsClick(true);
};
const handleMouseMove = (e: React.MouseEvent | React.TouchEvent) => {
if (!isClick) return;
setIsDragging(true);
console.log("isDragging : " + isDragging + " isClick : " + isClick);
console.log("모바일인가?");
console.log("touches" in e);
console.log("마우스 무브");
...
};
const handleMouseUp = (e) => {
if (!isDragging || !containerRef.current) return;
console.log("isDragging : " + isDragging + " isClick : " + isClick);
console.log("모바일인가?");
console.log("touches" in e);
console.log("마우스 업");
...
setIsDragging(false);
setIsClick(false);
};
위 코드를 간단하게 설명하면
isClick과 isDragging이라는 useState가 있고,
TouchDown과 MouseDown 이벤트가 발생하면 isClick은 True가 된다.
그리고 TouchMove나 MouseMove가 일어나면 handleMouseMove함수가 호출되고,
isClick으로 클릭이 된지 확인 후에 실행된다.
그래서 이때 isDragging으로 드래그인지를 저장한다.
마지막으로 TouchEnd나 MouseUP이 일어나면 handleMouseUp함수가 호출되고,
isClick과 isDragging 상태를 false로 초기화 시켜준다.
이런식으로 드래그 상태를 확인해서 Carousel 기능을 구현했다.
그럼 다시 터치 후 바로 떼는 동작으로 호출하는 함수 순서를 보자
touchStart -> mouseMove -> mouseDown -> mouseUp
이 순서대로 함수를 실행하게 된다면 아래 이미지 부분처럼 뭔가 이상한 것을 볼 수 있다.
나는 터치 후 바로 떼는 동작을 했는데도 mouseMove가 동작해서 isDragging 상태가 true가 된다.
그리고 MouseDown이 다음에 실행
그리고 MouseUp에서 isDragging은 true를 나타낸다.
즉 드래그를 하지도 않았는데 드래그를 했다고 하는 것이다.
(드래그 상태가 중요한 이유는 Carousel에서 드래그를 할 때는 내부의 Link 태그같은 클릭 이벤트가 발생하지 않도록 방지하려고 한 것임)
그래서 내가 아래와 같이 지정해준 Link태그를 막는 Css 속성이 적용돼서 터치 후 Link 기능이 안된 것이다.
<div
style={{
cursor: isDragging ? "grab" : "auto"
}}>
<div
style={{
gap: `${groupGap}px`,
pointerEvents: isDragging ? "none" : "auto"
}}
className="flex items-center justify-center flex-nowrap">
그럼 위 이벤트를 사용하는 곳에서 isDragging의 상태를 콘솔로그로 찍어보자
MouseUp이벤트가 일어날 때 isDraggin이 true다.
내가 추측하는 것은 Link태그는 MouseUp이 될 때 수행한다.(실험해보았음 확실치 않음)
그래서 isDragging이 true니까 css속성인 point-events가 none이 되어 Link를 누를 수 없게 되는 것이다.
그럼 어떻게 해결해야 돼?
일단 간단하게 생각하면 MouseEvent를 막으면 된다.
그럼 isTouch라는 useState를 만들어서 onTouchDown에 등록된 handleTouchDown이 실행되면
isTouch를 true로 만들어주고,
현재 터치이벤트인지 확인해서 isDragging이 바뀌는 MouseMoive 이벤트를 막으면 되지않을까? 생각했다.
그래서 아래와 같이 추가해주었다.
const [isTouch, setIsTouch] = useState(false);
// onTouchStart 이벤트 핸들러
const handleTouchDown = (e) => {
setIsTouch(true);
// ...
};
// onMouseMove 이벤트 핸들러
const handleMouseMove = (e) => {
if (!("touches" in e) && isTouch) {// 현재 터치이벤트가 아닌데, 터치이벤트로 시작됐다면
setIsTouch(false); // 다음 마우스 이벤트를 위해 상태를 초기화하고,
return; // handleMouseMove 함수를 바로 종료한다.
}
// ...
};
// 컴포넌트 마운트 해제 시, 터치 이벤트 상태를 초기화한다.
useEffect(() => {
return () => setIsTouch(false);
}, []);
위 코드로 바꾸고나서야 드디어 정상적으로 작동했다.
사실은 handleMouseMove 함수와 handleTouchMove함수를 따로 만들어서 해도 됐을 것 같다고 생각했다.
그래도 코드가 중복이 되면 안좋으니까 이 방법이 그나마 낫다고 판단했다.
지금은 이렇게 간단하게 끝냈지만 정말 많은 시도가 있었다..ㅠㅠ
e.preventDefault()가 나도 모르는 상속의 효과가 있는 것인가?
setTimeout을 이용해서 비동기 요청으로 Mouse이벤트를 뒤로 미루는 방법?
...
하여튼 다행이 useState하나로 해결했다.
이것때문에 State를 하나 더 관리해야한다는 점이 좀 그랬지만 어쩔 수 없었다 ㅠㅠ
참고 자료
https://soobing.github.io/browser/touch-mouse-event/
'TIL' 카테고리의 다른 글
NextJS 14 app router 동적 라우팅과 <img> src 버그- Next에 이슈 제기 (1) | 2024.03.29 |
---|---|
[2024-02-28] 팀프로젝트 개인회고 - Carousel의 내부 요소가 Link면 드래그할 때 Link 작동 막기, set함수를 호출하게 두면 안돼! (0) | 2024.02.28 |
[2024-02-27] 팀 프로젝트 개인회고 - 뭔가 부족한 Carousel 컴포넌트 (0) | 2024.02.27 |
[2024-02-23] 팀 프로젝트 회고 - useModal을 만들면서, 불필요한 렌더링 (0) | 2024.02.23 |
[2024-02-20] 팀 프로젝트 개인 회고 - 다크모드 플래시 문제 해결 2(쿠키) (0) | 2024.02.20 |