💻 카카오엔터테이먼트 FE 블로그를 참조하여 실습 및 정리한 내용입니다.
🧑🏻💻 코드 참고 : https://github.com/ckstn0777/pinch-zoom-practice
이번 시간에는 기본 브라우저 자체 핀치줌을 막아보는 방법에 대해 알아보도록 하겠습니다.
저는 원하는 영역에 대해서만 pinch zoom을 하면 해당 영역이 확대되거나 축소되었으면 좋겠습니다. 하지만 기본적으로 브라우저 자체에서 핀치줌을 하면 브라우저가 확대되어버린다는 문제가 있습니다. 따라서 브라우저 자체 핀치 줌을 막는 방법에 대해 알아보도록 하겠습니다.
예전에는 viewport meta 태그를 설정하는 방법으로 자체 핀치 줌을 막았다고 합니다. user-scalable을 no로 설정하거나, maximum-scale을 1.0으로 설정하면 됩니다.
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no" />
<!-- 또는 아래와 같은 방법 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0" />
💻 참고 : https://stackoverflow.com/questions/37808180/disable-viewport-zooming-ios-10-safari
하지만 이 방법은 iOS 10 이하에서만 가능한 방법이라고 합니다. 11부터는 더 이상 적용되지 않는 방법인데요... 크롬 기반인 웨일 브라우저에서도 해봤는데 똑같이 안되긴 합니다. 이럴거면 저 설정은 왜 있는걸까요...
MDN 문서를 보면 반드시 확대해야 하는 (시력 저하) 유저가 있을 수 있는데 이를 설정으로 쉽게 막아버릴 수 있다면 웹 접근성 측면에서 매우 안좋아지기 때문이라고 합니다.
그렇다고 아예 방법이 없는건 아닙니다. 자바스크립트 혹은 CSS로 제어가 가능합니다. 하지만 MDN에서도 말했다싶이 접근성을 잘 고려한 후 신중하게 결정해야 할 거 같습니다. 지금은 혼자하는 실습이라 큰 상관은 없지만...
"touchmove" 이벤트 발생 시에 멀티 터치(touches.length > 1)를 하고 있다면 기본 브라우저 핀치 줌 동작을 막기 위해 e.preventDefault를 사용해줍니다.
참고로 e.preventDefault 를 이용하면 해당 이벤트에 대한 사용자 에이전트의 기본 동작을 실행하지 않도록 지정 할 수 있습니다. 예를 들어, a 태그에서 preventDefault를 사용했다면 링크 이동을 하지 못합니다. input 에다가 적용하면 입력이 되지 않게 할 수도 있습니다.
// 브라우저 기본 pinch zoom 비활성화
function preventBrowserZoom() {
function listener(event: TouchEvent) {
// 핀치 줌의 경우 두 개 이상의 이벤트가 발생한다
if (event.touches.length > 1) {
event.preventDefault(); // preventDefault를 사용하려면 passive를 false로 해줘야합니다.
}
}
document.addEventListener("touchmove", listener, { passive: false });
}
preventBrowserZoom();
참고 : https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
평소에 볼 수 없었던 passsive: false
라는게 addEventListener에 사용되어서 한 번 찾아봤습니다. 문서에서 보는 것처럼 passive를 true로 하면 preventDefault를 사용할 수 없다고 합니다. 그리고 기본적으로 false이긴 한데, touchstart, touchmove, wheel, mousewheel 이벤트의 passive 옵션은 기본값이 true입니다. (단, 사파리에 경우에는 예외적으로 false 입니다)
그렇다면 도대체 저 옵션은 어디서, 어떻게, 왜 쓰이는 걸까요? 🤔
MDN 세부 사항 : Improving scrolling performance with passive listeners
참고 : https://stackoverflow.com/questions/37721782/what-are-passive-event-listeners
touch, wheel 이벤트 리스너에 대해 passive를 true로 설정하면 차단(block) 될 일이 없기 때문에 스크롤 성능을 아주 다이나믹하게 향상시킬 수 있다고 하네요.
음... 그니까 passive가 false로 되어있는 경우, touchstart, touchmove와 같은 핸들러 결과를 기다려야 됩니다. 이는 중간에 event.preventDefault를 통해 차단될 가능성이 있기 때문인데요. passive를 true로 설정하면 preventDefault를 호출하지 못하기 때문에 스크롤이 차단될 일이 없게 됩니다. 이를 통해 브라우저가 자바스크립트를 기다리지 않고 바로 스크롤에 반응할 수 있게 되어 사용자에게 안정적으로 부드러운 스크롤 경험을 보장합니다.
그래서 사파리를 제외한 브라우저에서는 Window, Document, Document.body의 touchstart와 touchmove 이벤트에 대해선 passive의 기본 값을 true로 바꿔서 적용하고 있습니다.
✍️ 사실 좀 익숙하지 않은 부분이라서... 새로운 사실을 알게 되었네요... 근데 요즘은 기기도 그렇고 브라우저 성능도 좋아서 별 차이 없는거 같기도 합니다...? 🤔
위에 자바스크립트를 이용한 방법으로 테스트를 해봤는데 유독 사파리에서만 특이한 이슈가 있습니다. 바로 손가락 하나로 먼저 터치를 한 후 조금씩 이동하고 있다가, 다른 손가락으로 터치해서 pinch zoom을 하면 핀치 줌이 되는 현상이 있습니다. (시간차공격?)
JavaScript 로직 상 이게 안되어야 하는게 맞는거 같은데... 진짜 이상하긴 합니다. touches length가 2개로 잘 나오고 있고... 크롬에서는 또 잘되니까 사파리 문제인거 같습니다.
아무튼 이런 문제도 해결할 수 있는 방법이 CSS에 있습니다. 스택오버플로우에서도 여러가지 방법 중 하나로 소개된 방법입니다. 바로 touch-action
속성을 이용하면 됩니다.
💻 참고 : https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action
“By default, panning (scrolling) and pinching gestures are handled exclusively by the browser.”
기본적으로 브라우저는 패닝(스크롤) 터치랑 핀치 줌 제어를 처리하고 있습니다. 근데 만약 이를 제어하고 싶다면? 에서 출발한게 바로 touch-action입니다. touch-action은 이를 조절할 수 있습니다.
body에 touch-action: none;
으로 해주면 브라우저 핀치 줌을 안되도록 할 수 있습니다. 하지만 스크롤도 안 되는 것을 알 수 있습니다.
/* css로 핀치 줌 제어, body 혹은 자동으로 scale 되는 대상에 적용 */
.preventTouch {
touch-action: none;
}
하지만 이는 세부적인 옵션으로 조정이 가능하므로, touch-action: pan-x pan-y;
를 사용하면, 핀치 줌만 막고 좌우, 상하 스크롤은 허용하도록 할 수 있습니다.
이번 시간에는 브라우저 자체 핀치 줌 기능을 막는 방법에 대해 배워봤습니다. 그 과정에서 여러가지 새로운 사실을 알게 되었습니다. viewport meta scale 조정으로는 더 이상 막을 수 없고, JavaScript를 활용할 수 있지만 사파리에서 약간 아쉬운 점이 있으며, CSS touch-action을 통해 제대로 막을 수 있었습니다.
다음 시간에는 본격적으로 핀치 줌 기능을 구현에 대해 알아보도록 하겠습니다.