02. 모바일 100vh 스크롤 문제 해결하기
문제발견
모바일 화면에서 전체화면을 만들기 위해 100vw, 100vh로 설정을 했다. 브라우저에서 확인 하면 이상이 없지만, 실제 모바일 브라우저에서는 스크롤이 생기는 현상이 발견되었다...!
vw와 vh
우리는 종종 스크린 사이즈의 너비를 혹은 높이 이용해 css 스타일링을 하곤 한다.
이 때 사용하는 단위가 바로 vw, vh 인데 1vw는 스크린 너비의 1%, 1vh는 스크린 높이의 1%를 말한다.
다시 말해, 100vw는 스크린 가로 사이즈, 100vh는 스크린 세로 사이즈를 의미한다.
이번 mbti 페이지에는 이 vw, vh라는 단위를 자주 사용했다.
브라우저의 관리자 모드에서 모바일 환경에서의 해상도를 미리 확인 해 볼수 있는데,
처음에 우리는 이 모드로 계속해서 확인 하면서 화면에 딱 맞는 크기로 페이지를 구현 하려고 했다.
때문에 화면 전체를 감싸는 viewer의 크기를 다음과 같이 설정했다.
.viewer {
width: 100vw;
max-width: 580px;
height: 100vh;
overflow: hidden;
margin: 0 auto;
}
사실 viewer 뿐만 아니라 스크린의 높이가 필요한 부분에 모두 100vh라는 값을 사용했다.
하지만, 배포를 하고 실제로 모바일에서 앱을 열어보니 예상치 못한 결과가 나왔다.
viewer의 사이즈가 실제 화면보다 크게 잡혀서 스크롤이 생겨버린 것이다.
검색 해보니 원인은 모바일 브라우저에서 100vh는 상단 url을 입력하는 영역과 하단 네비게이션 영역의 사이즈를 포함하기 때문이었다.
그렇다면 내가 원하는 영역인 bar영역을 제외한 스크린 사이즈의 크기를 어떻게 가져올 수 있을까?
CSS의 사용자 정의 속성과, 자바스크립트로 이를 해결 할 수 있다.
자바스크립트에서 window.innerWidth와 window.innerHeight는 현재 뷰포트의 크기를 가져올 수 있다.
(참고 - window.screen.width, window.screen.height는 OS의 해상도의 크기를 나타낸다)
이를 이용해서 사용자가 페이지에 접근했을 때,
자바스크립트의 가장 상단에서 innerHeight의 1%의 값을 구해 --vh라는 속성으로 새로 정의하는 함수를 만들어 호출해준다.
funciton setScreenSize() {
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
setScreenSize();
자, 이제 기존에 사용했던 vh단위를 모두 --vh로 바꾸기만 하면 된다.
height: calc(var(--vh, 1vh) * 100);
var(--vh)가 아니라 var(--vh, 1vh)는 뭐야?
var() 함수에 대해 잠깐 짚고 넘어가자면 root에서 설정해 둔 사용자 지정 속성의 이름을 가져와 해당 값을 그대로 사용할 수 있도록 한다.
(다른 곳에서 지정할 수도 있지만 일반적으로 전역적으로 값을 설정하기 위해 root에 선언한다.)
예를 들면 아래와 같이 사용 할 수 있다.
:root {
--myColor: #333;
}
.body {
background-color: var(--myColor);
}
여기서, 함수의 첫 번째 인수는 대체 할 사용자 지정 속성의 이름이고, 두 번째 인수는 대체 값으로 사용된다.
첫 번째 인수에서 참조하는 사용자 지정 속성이 유효하지 않은 경우 함수는 두 번째 값을 사용한다는 뜻이다.
두 번째 인수는 생략이 가능하다.
우리의 --vh는 자바스크립트에서 설정을 해주기 때문에 자바스크립트의 해당 구문이 실행되기 전 까지
--vh를 1vh로 설정을 해 준것이다.
확인을 해 보니 아주 잘 작동했다 🙂
스크린 사이즈가 변경 될 때마다 --vh의 크기 변경 해주기
이제 정말 완성 된 걸까??
사실, 이렇게 하면 스크린 사이즈를 맨 처음에 딱 한번만 실행시켜 주기 때문에, 스크린 사이즈가 변경 될 때 앱 화면의 크기는 변하지 않는다.
위 문제를 해결하기 위해 이벤트를 수신해서 스크린의 사이즈가 변경되었을 때에도 vh를 새로 정의했다.
window.addEventListener('resize', () => setScreenSize());
카카오 인앱 브라우저에서의 문제
서비스 특성상, 카카오톡으로 공유가 활발히 이루어져서 카카오톡 위의 브라우저에서 사용할 것이라 생각해 카카오 인앱 브라우저에서의 테스트도 진행을 해봤는데 (또) 문제가 생겼다.
카카오 브라우저에서 확인을 해 보니 화면의 크기가 스크롤에 의해 변하는 것 이다.
(각 요소의 크기는 딱 맞춰서 잘 나오는데 아래로 스크롤이 생겼다.)
아마 문제의 원인은, 카카오 브라우저의 경우, 스크롤을 하면 상단 주소창과, 하단 네비게이션 영역이 스크롤에 의해 사라지기 때문인데,
이를 해결하기 위해서는 어떻게 해야할까?
1. 인앱 브라우저를 열었을 때, 문제가 없는 브라우저인 chrome이나 safari등 모바일의 기본 브라우저를 열어준다.
2. body의 스크롤을 막는다.
1번의 방법은 다른 블로그에서 잘 설명해주셨으니 이를 참고하자.
body 스크롤 막기
모바일에서 스크롤을 막기 위해서는 세 가지 방법이 있다.
1. overflow와 touch-action 설정
body 부분에 overflow를 hidden으로 설정하면 콘텐츠의 크기가 화면에 알맞게 설정이 되고,
touch-action을 none으로 설정하면 브라우저에게 맡길 터치 액션을 모두 없앤다.
body {
overflow: hidden;
touch-action: none;
}
2. position: fixed 설정
위 방법을 사용하면 일반 브라우저에서는 스크롤이 안 막히지만, 문제의 카카오톡 인 앱 브라우저에서는 여전히 스크롤이 작동한다.
때문에 두 번째 방법으로 body의 스타일에 position: fixed를 추가로 설정해보았다.
body {
position: fixed;
overflow: hidden;
touch-action: none;
}
3. 스크롤 이벤트 자체 제거
여전히 스크롤이 잘 작동한다... 무엇이 문제일까..?
자바스크립트로 스크롤 이벤트를 아예 제거해보았다.
모바일 스크롤을 막기 위해서는 touchmove, onclick, mousewheel 이 세가지 이벤트를 모두 막아 줘야한다.
disableScroll = () => {
document.querySelector('body').addEventListener('touchmove', this.removeEvent, { passive: false });
document.querySelector('body').addEventListener('onclick', this.removeEvent, { passive: false });
document.querySelector('body').addEventListener('mousewheel', this.removeEvent, { passive: false });
}
removeEvent = e => {
e.preventDefault();
e.stopPropagation();
}
enableScroll = () => {
document.querySelector('body').removeEventListener('touchmove', this.removeEvent);
document.querySelector('body').removeEventListener('onclick', this.removeEvent);
document.querySelector('body').removeEventListener('mousewheel', this.removeEvent);
}
결과 페이지에는 스크롤이 다시 가능해져야하기 때문에 이벤트를 지워줬다.
그 결과 드디어 카카오 인앱 브라우저에서도 스크롤이 발생하지 않고 원하는 화면이 올바르게 나왔다..! (감격)
포스팅은 짧지만 이 문제를 해결하기 위해 이틀이 넘는 시간이 걸렸다...
그리고, PC 브라우저에서는 확인이 불가능한 문제였기 때문에 계속 배포를 하고 배포된 버전을 확인하는 작업을 거쳤기 때문에
더 고단한 문제 해결 과정이었다...
이번 기회에 다양한 앱 서비스가 나오면서 함께 나올 인앱 브라우저에서도 문제없이 서비스가 작동할 수 있도록 호환성 체크를 꼼꼼히 해줘야겠다는 생각이 들었다. 🙂
참고