배경
📐
개발 •  • 읽는데 7분 소요

안드로이드 크롬에서 CSS vh 단위가 이상하게 동작했던 이유

안드로이드 크롬 108 버전부터 레이아웃 표시 영역의 동작이 달라졌기 때문입니다.

#Front-End


작년에 회사에서 QA를 진행하다가 이상한 버그를 발견했습니다. 구형 안드로이드 기기에서 가상 키보드가 올라오면 CSS 단위인 vh 값이 예상과 다르게 동작하고 있었던 것이죠. 그런데 비교적 최신 기기였던 제 스마트폰에서는 정상적으로 동작하더군요.

두 스마트폰에서 다른 부분이 보이시나요?

이상하다 싶어서 원인을 찾아보니, 안드로이드 크롬 108 버전부터 브라우저가 레이아웃 표시 영역을 계산하는 방식이 바뀌었기 때문이었습니다. 제 스마트폰은 크롬 108 이상이었기 때문에 정상 동작했던 것이었죠.

솔직히 이런 변경 사항이 있었던 걸 몰랐다는 점이 꽤 충격(?)이었는데요. 그래서 이번 기회에 레이아웃 표시 영역이란 무엇인지, 그리고 크롬 108부터 기본 동작이 왜 변경되었는지 정리해 보려고 합니다.

이번 포스트를 통해 안드로이드 크롬에서 뷰포트 관련 문제를 겪고 있는 개발자 분들에게 도움이 되었으면 좋겠습니다.

문제 상황 살펴보기

다른 그림 찾기 좌측은 크롬 버전 103, 우측은 크롬 버전 123

처음에 나온 영상을 자세히 살펴보면 두 스마트폰에서 다른 부분을 찾을 수 있습니다.

첫 번째는 바로 document 의 높이입니다. 좌측은 가상 키보드가 올라오면 높이가 줄어드는 반면, 우측은 가상 키보드가 올라와도 높이가 그대로 유지되고 있습니다.

정답 두 문제의 정답은

두 번째는 바로 input 의 위치입니다. 이 인풋은 top: 20vh 스타일을 갖고 있는데요. 좌측은 가상 키보드가 올라오면 인풋의 위치가 올라가는 반면, 우측은 가상 키보드가 올라와도 인풋 위치가 그대로 유지됩니다. 이를 통해 vh 단위가 두 스마트폰에서 다르게 동작하고 있다는 것을 알 수 있습니다.

이러한 차이점이 왜 발생하는지 이해하기 위해서는, 먼저 레이아웃 뷰포트시각적 뷰포트의 개념을 알아야 합니다.

레이아웃 뷰포트와 시각적 뷰포트

레이아웃 뷰포트 레이아웃 뷰포트를 파란 영역으로 나타냈다

레이아웃 뷰포트(layout viewport) 란 브라우저에 화면이 나타내는 영역을 의미합니다. 만약 우리가 접속한 웹 페이지에 스크롤이 있다면, 우리는 레이아웃 뷰포트 영역만큼을 화면에서 볼 수 있게 됩니다.

레이아웃 뷰포트

레이아웃 뷰포트는 position: fixed 스타일을 갖는 요소에 영향을 줍니다. 화면에 고정된 위치를 지정하는 fixed 스타일은 레이아웃 뷰포트를 기준으로 계산되기 때문에, 레이아웃 뷰포트의 크기가 변하면 fixed 스타일을 갖는 요소의 위치도 변하게 되죠.

브라우저 주소창, 가상 키보드, 핀치 줌을 이용해 레이아웃 뷰포트의 크기를 변경시켜 보았다

반면 시각적 뷰포트(visual viewport) 란 사용자가 지금 실제로 보고 있는 화면의 영역을 의미합니다. 브라우저의 주소창, 가상 키보드의 영역, 핀치 줌을 이용한 화면 확대 등 다양한 요소로 인해 레이아웃 뷰포트와 눈에 보이는 영역이 서로 다를 수 있기 때문입니다.

위 영상을 보시면 이해가 빠르실 것 같은데요, 화면에 나타나는 Visual Viewport 영역의 값 변화를 살펴보시면 됩니다.

OS와 브라우저에 따라 달라지는 동작

이렇게 해서 레이아웃 뷰포트와 시각적 뷰포트의 차이점을 알아봤습니다. 그리고 모바일 브라우저에서 가상 키보드가 올라오는 것이 시각적 뷰포트의 크기를 변경시킨다는 것도 알게 되었죠.

여기서 모바일 브라우저는 가상 키보드가 올라올 때 세 가지의 방법으로 레이아웃 뷰포트와 시각적 뷰포트를 계산할 수 있습니다. 여기서 문제가 발생하죠. 바로 OS와 브라우저의 조합에 따라 동작이 서로 달라진다는 것입니다.

브라우저 브라우저 별 레이아웃 뷰포트와 시각적 뷰포트의 변경

  • resize-visual 방식
    • 레이아웃 뷰포트는 변경되지 않고 시각적 뷰포트만 변경
    • iOS 크롬, iOS 사파리, iOS 엣지, iPad 크롬, iPad 사파리, iPad 엣지, ChromeOS 크롬
  • resizes-content 방식
    • 레이아웃 뷰포트와 시각적 뷰포트 모두 변경
    • 안드로이드 크롬 (108 버전 이전까지), 안드로이드 파이어폭스, 안드로이드 엣지
  • overlays-content 방식
    • 레이아웃 뷰포트와 시각적 뷰포트 모두 변경하지 않음
    • 해당 방식으로 동작하는 브라우저 없음

이런 차이로 인해 가상 키보드가 올라올 때 안드로이드 크롬과 iOS 크롬에서 서로 다른 동작을 하는 문제가 발생합니다.

  • iOS 크롬에서는 vh 단위가 유지되는 반면 안드로이드 크롬에서는 vh 단위가 변경됨
  • position: fixed 스타일을 갖는 요소의 위치가 iOS 크롬에서는 유지되는 반면, 안드로이드 크롬에서는 변경됨

이를 올바르게 해결하기 위해서는 사용자 에이전트를 읽어와서 JavaScript로 레이아웃 뷰포트와 시각적 뷰포트의 크기를 직접 계산해야 하는데, 개발자 입장에서는 여간 번거로운 일이 아니었죠.

안드로이드 크롬 108 버전부터 기본 동작 변경

이처럼 동일한 코드가 OS에 따라 서로 다르게 동작하는 문제가 있었기 때문에, 안드로이드 크롬 108 버전부터는 iOS의 동작 형태를 그대로 따라가도록 수정되었습니다. 즉 눈에 보이는 화면 크기가 줄더라도 레이아웃 영역 계산에 영향을 주지 않게 되었습니다. 이로 인해 다른 브라우저와의 동작 차이를 줄이고, vh 와 같은 뷰포트 단위의 동작을 일관되게 사용할 수 있게 되었죠.

만약 108 버전 이후의 안드로이드 크롬에서 기존의 동작을 유지하고 싶다면 다음과 같은 메타 태그를 사용하면 됩니다. interactive-widget 속성에 원하는 동작을 지정해주면 되죠.

<meta
  name="viewport"
  content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>

새로운 뷰포트 단위의 탄생

이렇게 해서 안드로이드 크롬 108 버전부터 레이아웃 표시 영역의 기본 동작이 변경되었다는 소식을 전해드렸는데요. 마침 해당 버전에 뷰포트와 관련된 새로운 기능이 추가되어서 이를 함께 소개해 드리려고 합니다.

모바일 모바일 브라우저의 주소창과 탭 바의 유무에 따라 100vh 의 크기는 정확히 화면과 일치하지 않는다

모바일 브라우저에서는 주소창과 탭 바 등의 요소가 화면의 일부를 차지하게 되는데요. 이러한 요소들이 화면의 일부를 차지하게 되면 100vh 단위로 높이를 지정했을 때, 화면의 높이와 일치하지 않는 문제가 발생합니다.

특히 모바일 크롬을 예시로 들면… 화면을 아래로 스크롤하면 주소창이 사라지고, 가상 키보드가 올라오면 주소창이 다시 나타나는 것이 기본 동작입니다. 이러한 동작으로 인해 vh 단위로 스타일을 지정한 요소가 원하는 위치에 나타나지 않는 문제가 발생하는데, 이걸 고쳐 달라는 요청을 들으면 참 난감했거든요.

동적 뷰포트 단위 이로 인해 등장한 단위, dvh lvh svh

이러한 문제를 해결하기 위해 새로운 뷰포트 단위가 등장했습니다. 바로 dv*, lv*, sv* 단위인데요. 이 단위들은 뷰포트의 크기를 기준으로 계산되는데, 각각 다음과 같은 특징을 갖고 있습니다.

  • dv*: 현재 레이아웃 뷰포트의 크기를 기준으로 계산
  • lv*: 가장 큰 레이아웃 뷰포트의 크기를 기준으로 계산
  • sv*: 가장 작은 레이아웃 뷰포트의 크기를 기준으로 계산

이러한 접두어는 vw, vh, vmin, vmax 와 같은 뷰포트 단위와 함께 조합하여 사용할 수 있으며, 레이아웃 뷰포트의 크기에 따라 동적으로 변하는 뷰포트 단위를 사용할 수 있게 되었습니다.

동적 뷰포트 단위 브라우저 지원 범위

브라우저 지원 범위가 94%로 높긴 하지만 구형 브라우저에서는 지원이 되지 않기 때문에 조심해야 한다는 점을 강조하면서 이번 내용을 정리할 수 있을 것 같네요.

마무리

chrome

이렇게 해서 이번 포스트에서는 레이아웃 표시 영역이란 무엇인지, 그리고 크롬 108부터 기본 동작이 왜 변경되었는지를 정리해 보았습니다.

저는 위 내용을 팀에 공유했고, 안드로이드 크롬의 버전이 108 이상인지 미만인지에 따라 로직을 분리하는 방식으로 QA에 제보된 문제를 해결할 수 있었습니다.

혹시라도 여러분이 웹 개발 도중 구형 안드로이드 기기의 크롬에서만 뷰포트 관련 문제가 발생한다면, 이를 근거로 문제의 원인을 찾아보세요.

참고 링크

이 포스트가 유익하셨다면?




프로필 사진

👨‍💻 정종윤

글 쓰는 것을 좋아하는 프론트엔드 개발자입니다. 온라인에서는 재그지그라는 닉네임으로 활동하고 있습니다.


Copyright © 2025, All right reserved.

Built with Gatsby