세상엔 참 당연하지만 당연하지 않은 것들이 많다. PWA 스크롤이 그렇다. 네? 당연히 페이지 전체는 안 움직어야 하는데 왜 움직이는가? 그리하여 시작되는 삽질.


Standalone의 Viewport

Viewport 시작점

아마 PWA를 쓰는 사람들은 분명 Status Bar까지 꽉 채우는 그림을 생각하고 적용을 하는 경우가 많을 것 같다. 하지만~ standalone의 기본은 Status Bar 아래부터 시작한다.

이걸 채우기 위해선 meta 태그의 viewport-fit 속성을 cover로 바꿔주면 되는데.... 이때부터 지옥이 시작된다.

<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />

어? 너 왜...?

CSS env()

viewport-fit 속성은 보다시피 Viewport가 Status Bar부터 시작하게 만들어준다. 아하! 그러면 Status Bar의 Height만큼 떨어지게 하면 되겠구나! 아이패드 Status Bar Height를 구해서 추가해주자.

html { padding-top: 20px; }

별 거 아니구만! 우하하

어라

그렇다면 다시 한번 구해서...!

..같은 미련한 짓을 하지 않기 위해 CSS에서는 env() 라는 함수를 제공하고 있다.

The env() CSS function can be used to insert the value of a user-agent defined environment variable into your CSS, in a similar fashion to the var() function and custom properties. The difference is that, as well as being user-agent defined rather than author-defined, environment variables are globally scoped to a document, whereas custom properties are scoped to the element(s) on which they are declared.

다 제끼고 저 문장만 보면, User agent가 정의한 환경 변수를 제공해준다는 말이다. 이 User Agent라는 건 접속한 브라우저의 타입 정도로 생각하면 된다. 그리고 iOS의 Safari 브라우저는 safe-area-inset-* 라는 환경 변수를 제공하는데, 이게 바로 Status Bar의 크기 되겠다. (정확히는 내부 콘텐츠가 안전하게 보여질 수 있는 위치)

safe-area-inset-top

그 중에서 safe-area-inset-top를 사용하면 된다.

html { padding-top: env(safe-area-inset-top); }

안전하게 잘 들어왔다!

참고로 margin이 아닌 padding으로 준 이유는, 기본적으로 margin은 객체 자체를 밀어버리기 때문. 그래서 스크롤이 생기기도 하고, padding으로 줘야 내부 콘텐츠의 크기를 잡기가 편하다.

전체 화면

기본적으로 어플은 화면 꽉차게가 제맛. 이를 위해서 아래와 같이 적용해보자. 알아보기 쉽게 border를 적용했다.

html { width:100%; height: 100vh }

얼핏 보면 적용이 된 것 같은데~ 문제가 있다. 오른쪽을 보면 화면이 아래로 더 나가버린다. 이는 100vh의 기준이 브라우저 도구툴 영역까지 포함하기 때문에 나타나는 현상이다. 이에 대한 대응으로 이전에 포스팅해둔 dvh를 참고!

dvh를 적용하고 확인해보면~

왼쪽 화면은 올라가게 됩니다! 와! ㅋㅋ

아이러니 하게도.. dvh는 safe area를 반영한 크기이고, safe area는 아래의 검은색 바를 포함하고 있어서 나타는 결과이다. 이자식이!

이를 해결하려면 미디어 쿼리를 사용해서 따로 처리해주는 게 제일 간편한 것 같다.

display-mode

standalone으로 열게 되면 display-mode라는 걸로 알 수 있다. 그래서 다음과 같이 설정해주자.

html {
 height: 100dvh;

  @media all and (display-mode: standalone) {
    height: 100vh;
  }
}

음 그래그래 진작 그럴 것이지

전체 스크롤 방지

아직 난관이 남아있다.

그것은 바로 화면 전체가 스크롤이 된다는 점이다. 이를 막기 위해서는 인터넷에서 아주 여러 야매 방법들이 돌아다니는데, 유명한 거랑 내가 선택한 방법을 소개해보겠다.

Position: fixed

요 방법은 캡처하기가 애매해서 말로만 설명하겠다. 뭐.. 결과적으로 말하자면 스크롤이 방지'된 것처럼' 보인다.

  • standalone에서는 html이 fixed가 되면서, 상위에 맥락적으로 safe area를 포함한 부모가 하나 더 생성되는 느낌? 그래서 그게 fixed된 객체를 가려버린다.
  • 일반 브라우저에서는 위로 드래그는 막지만, 아래로 드래그는 Safari 자체의 pull to refresh 기능 때문에 여전히 화면이 밀린다.

그러므로 이 방법은 땡!

touch-action: none

touch-action을 없애버려서 제스처 자체가 안 먹히게 하는 방법이다. 스크롤 방지가 되지만 접근성에서 문제가 생긴다(카더라)

  • 브라우저의 확대 / 축소 작동을 제한하여 텍스트의 크기를 고정하기 때문에, 사용자의 접근성을 보장하지 못하는 문제를 가지고 있습니다.
  • 해당 내용 관련하여 IOS 10버전 이후, 애플은 접근성 문제로 인해 user-scalable=no를 의도적으로 비활성화했습니다.
  • 때문에 기존처럼 meta 태그에 user-scalable=no를 추가하여도 사용자 휴대폰 설정에 확대 축소 가능으로 되어있다면 이를 제한할 수 없게 되었습니다.
  • 출처: https://wit.nts-corp.com/2021/07/16/6397

나의 목적은 확대/축소를 막는 것이 아니라 스크롤을 막는 게 목적이기 때문에, 이 방법도 땡! 만약 아예 확대/축소도 안 되게 고정되게 하려면 요 방법도 나쁘진 않겠다.

overscroll-behavior: none

이것이 내가 선택한 방법. 확대/축소가 되면서 단순히 스크롤만 막아준다.

html { overscroll-behavior: none; }
body { overflow-y: auto; }

html에는 스크롤을 막아주면서, 우리 내부의 바디에는 스크롤이 동작하게끔 해줘야 하므로 body에는 overflow 속성을 지정해주어야 한다.

html style

그러므로~ 지금까지 정리한 스타일들은 다음과 같다.

앱처럼 느껴지게 하는 스타일

  • Status Bar 위치 대응
  • 전체 화면
  • 전체 화면 스크롤 방지

코드는 다음과 같다.

html {
  width: 100%;
  height: 100dvh;
  padding-top: env(safe-area-inset-top);
  overscroll-behavior: none;

  @media all and (display-mode: standalone) {
    height: 100vh;
  }
}

body {
  overflow-y: scroll;
}

물론 body의 overflow-y 속성은 없어도 된다. 따로 내부에 overflow를 지정해주면 스크롤이 되긴 함!


  • 여러 환경에서 같은 화면을 보여준다는 것.. 그것이 크로스 브라우징이니까.

profile
FE개발자 가보자고🥳

0개의 댓글