Javascript의 void operator 에 대하여 알아봅니다.
모바일 애플리케이션 웹뷰에 사용되는 웹 콘텐츠의 터치 이벤트 사용과 소소한 팁
2019-03-09
Explanation
이번 글은 회사에서 회사 동생과 모바일 애플리케이션의 웹뷰에 사용되는 웹 콘텐츠를 개발하며 알게된 소소한 터치 이벤트에 대해서 정리해 보려 합니다.
1 |
body { -webkit-overflow-scrolling: touch; } |
안드로이드의 웹뷰 영역과 달리 iOS 웹뷰의 영역은 스크롤이 있는 영역에서 위와 같은 css를 주어야 터치로 인한 스크롤에서 스크롤에 가속도를 줄 수 있습니다. 만약 저 속성을 주지 않으면 딱 터치로 움직인 만큼 스크롤이 이동되기 때문에 버벅이는 것처럼 느껴집답니다.
일반 적으로 PC 기반으로 개발을 하다보면 ‘click’ 이벤트를 많이 사용하게 되는데요, 모바일 웹뷰 영역에서는(특히 iOS) click 이벤트가 한박자 늦게 실행되는 것을 느낄 수 있답니다. 처음에는 네이티브 앱 위에서 동작하기에 이벤트가 전파되는 속도가 느린가?? 생각했는데, 또 생각해보니 터치로 스크롤은 바로 반영이되는 부분이 이상하더라고요?
그리고 좀 실험해보니, 모바일 웹뷰에서는 터치엔드로 시작해서 클릭 이벤트로 실행되는게 PC에서 마우스업에서 부터 클릭 이벤트가 실행되는 그 속도?시간?에 차이가 있는 것 같습니다.(특히 iOS) 예를 들면 화면을 터치하면 아래와 같은 순서로 이벤트가 발생하는데요.
1 |
tocuhstart > touchend > mousemove > mousedown > mouseup > click |
그래서 모바일 웹뷰 환경에서는 click 이벤트보다 touchend 이벤트를 사용하는 것이 훨씬 빠르게 이벤트가 동작합니다.
하지만… 그냥 touchend 이벤트를 사용하려면 몇가지 문제의 상황에 봉착하는데요. 그 첫번째로 스크롤을 하기 위한 터치를 할때, 그 타겟이 터치 이벤트에 추가되어 있다면 스크롤이 끝나고 터치 이벤트가 호출된다는 것 입니다.
그러니까.. click 이벤트는 touchstart 이벤트가 발생하고 touchmove 이벤트가 호출되지 않고 touchend가 실행되었을때 click 이벤트가 호출되는데, touchend는 touchmove 이벤트 발생 유무와 상관없이 터치가 끝나는 시점에 무조건 실행이 되는 것이지요.
그래서 터치로 스크롤이 발생할때는 touchend 이벤트가 발생하지 않도록 따로 설정을 해줘야 한답니다.
예를 들면 아래와 같은 코드가 될 거 같아요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const contentArea = document.getElementById('scroll-area'); let scrollX = 0; let scrollY = 0; let touchPositionX = 0; let touchPositionY = 0; document.addEventListener('touchstart', function (e) { scrollX = contentArea.scrollLeft; scrollY = contentArea.scrollTop; touchPositionX = e.touches[0].screenX; touchPositionY = e.touches[0].screenY; }); document.addEventListener('touchend', function (e) { if (touchPositionX !== e.changedTouches[0].screenX || touchPositionY !== e.changedTouches[0].screenY || scrollY !== contentArea.scrollTop || scrollX !== contentArea.scrollLeft) { e.stopPropagation(); } }, { capture: true }); |
touchstart 이벤트가 발생하면, 이벤트 발생할때의 스크롤의 위치와 터치한 위치를 기억하고 있다가, touchend 이벤트가 발생했을때의 스크롤의 위치와 터치한 위치를 비교해서 같지 않으면 touchend 이벤트가 전파되는 것을 막는 코드이고요.
아래의 capture 속성은 기본 값은 false 인데요, true이면 이벤트가 부모에게서 자식 순으로 이벤트가 전파되거든요. 위 코드상 이벤트를 document에 주었기 때문에 e.stopPropagation();로 이벤트 전파를 막았기 때문에 이후 자식 요소들에게 touchend 이벤트가 발생되지 않습니다.
그리고 두번째로 touchend 이벤트를 사용하면 겪을 수 있는 문제가 있는데요, 만약에 터치 이벤트로 새로운 레이어 화면을 띄우는데, 새로운 레이어에 딱 터치 이벤트가 발생한 위치에 input이나 textarea 같은 태그가 존재한다면 앞서 이야기한 이벤트 순서에 따라서 input이나 textarea 영역에 포커싱이 된답니다.
1 |
tocuhstart > touchend > mousemove > mousedown > mouseup > (focus) > click |
한번 더 적으면 touchend 이벤트가 발생하고 그 이벤트로 새로운 dom요소가 생기고, 처음 터치 이벤트를 실행한 위치에 input, textarea 엘리먼트가 있다면 그다음 foucs(+ click) 이벤트가 발생해서 input, textarea에 포커스가 바로 된답니다.
그리하여 이 문제를 해결하기 위해 생각해 본 방법을 아래와 같습니다.
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 |
const contentArea = document.getElementById('scroll-area'); let scrollX = 0; let scrollY = 0; let touchPositionX = 0; let touchPositionY = 0; let touchStartEl; document.addEventListener('touchstart', function (e) { scrollX = contentArea.scrollLeft; scrollY = contentArea.scrollTop; touchPositionX = e.touches[0].screenX; touchPositionY = e.touches[0].screenY; }); document.addEventListener('touchend', function (e) { if (touchPositionX !== e.changedTouches[0].screenX || touchPositionY !== e.changedTouches[0].screenY || scrollY !== contentArea.scrollTop || scrollX !== contentArea.scrollLeft) { e.stopPropagation(); } else { touchStartEl = e.target; } }, { capture: true }); document.addEventListener('click', function (e) { const target = e.target; if (touchStartEl !== target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')) { target.blur(); } }, { capture: true }); |
touchend 이벤트가 발생하고 스크롤이나 터치 위치가 변하지 않았을때는 해당 타겟 엘리먼트를 기억하고 있다가, 이벤트 순서에 따라 마지막쯤 click 이벤트가 실행 되었을때 타겟의 엘리먼트가 바뀌었고 그 타겟의 엘리먼트가 input 이거나 textarea 라면 blur(포커스 아웃)시킵니다.
끝