본문 바로가기

Front-End/Safari

Safari on IOS 에서 주소창 고정이 되거나 status bar 맨 위로 이동이 되지 않는 경우

최근 회사에서 모바일 웹 페이지를 개발하다가 발견한 이슈인데, 어디에서도 정보를 찾기 힘들어서 까먹지 않기 위해 기록을 남긴다.

구현해야하는 기능중, 원할때 위에서 아래로 스와이프 하여 팝업을 닫을수 있는 인터렉션을 구현해야했는데, 하필 이 팝업은 화면을 꽉 채우는 팝업 이었다.

이미지와 같이, 맨위의 바를 당겨서 팝업창을 끄는 인터렉션을 구현해야 했다.

이 뒤의 배경에 스크롤 가능한 요소가 자리잡고 있고, 위에 모달 요소와 팝업 요소가 올라가는 형태이다.

그래서 스크롤 하여 원문을 보다가 팝업을 여는 버튼을 클릭시 나타나므로, 보고 있는 뷰포트와 스크롤을 유지한채 화면 전체를 잠궈야 했다.

보통 이런 팝업에서 위에서 아래로 스크롤 하게되면 뒤의 배경이 따라 움직이는 경우가 많다.

대부분 이런 문제를 해결하기 위해 구글링 하게 되면 부모요소에 position : fixed 나 touch-action : none 이나 overflow : hidden 등을 주라는 식의 조잡한 해결책 밖에 없고....

webkit 속성이나 scroll-behavior 등을 사용하라는 글도 많은데 이런 속성들은 IOS 15 + 버전에서는 지원하지 않는다.

다른 방법을 시도한것은 Inobounce.js 를 가져와 사용하던지 했지만,,,,, Inobounce 도 15 + 버전에서는 쓸모가 없다.

 

또 html, body, #root 등이 스크롤이 되지 않게 했더니, 문서의 스크롤이 잡히지 않아 최상단으로 인식이 되는지 

당겨서 새로고침이 같이 인터렉션 되버리는 문제가 있었다. 😥

그래서 다양한 방법을 시도 하다가, 아래와 같은 방법으로 당겨서 새로고침 자체는 해결이 되었다.


  
html{
height: 100%;
overflow-y: auto;
}
body {
height: 100%;
overflow-y: hidden;
}

  
#root {
position: relative;
overflow-y: auto;
height: calc(100vh - 1px);
}

  
// JavaScript
// 팝업을 관리하는 Container Component 에서 동적으로 style을 할당한다.
const $html = document.body.parentElement;
...
if (popup.locationPopup === true) {
let scrollPosition = $root.offsetTop;
document.body.style.overflowY = "hidden";
$html.style.overflowY = "hidden";
$root.style.overflowY = "hidden";
$root.style.touchAction = "none";
$root.style.pointerEvents = "none";
$root.style.top = `-${scrollPosition}px`;
$root.style.left = "0";
}
if (popup.locationPopup === false) {
document.body.style.overflowY = "auto";
$html.style.overflowY = "auto";
$root.style.overflowY = "auto";
$root.style.removeProperty("touch-action");
$root.style.removeProperty("pointer-events");
$root.style.removeProperty("top");
$root.style.removeProperty("left");
}

 

이제와서 생각하는 거지만 아무래도 이런 단순한 방법으로는 어려울것 같고 본문이 담긴 dom 요소의 배치나 이벤트 핸들링으로 실제 터치요소에만 인터렉션이 적용되는 등 다른 방법을 시도 했어야 할것 같은데, 시간이 많지 않았다.

여튼 이런 방법들을 통해 당겨서 새로고침 자체는 위의 팝업이 나타나 있는 동안에는 작동하지 않는다.

문제는 스크롤시 주소창이 줄어드는 safari 의 전체화면 모드와 status bar 터치를 통한 최상단 이동등의 Safari Native 기능들이 작동하지 않는것 이었다.


  
<!-- 전체화면 모드의 적용자체는 아래 메타태그를 적용하면된다. 애플 사파리 공식문서에 나와있다. -->
<meta name="mobile-web-app-capable" content="yes">

원인을 먼저 파악해보자면, 전체화면 모드는 스크롤 가능한 요소가 화면의 뷰포트 보다 클때 동작하는데, 이 경우엔 전체 엘리먼트를 래핑한 #root 요소의 height 가 100vh - 1px 로 되어있기때문에, html 이나 body 도 그 크기 만큼만 잡힌다.

근데 이런 점이 status bar 기능에도 영향이 있는지, 최상단 스크롤 이동 마저 되지 않는 것이다.

생각해보면 html 과 body 의 height 가 뷰포트와 동일한데, 작동이 안되는것이 당연해보이기도 한다.

또한 html 에 overflow-auto 속성이 있기 때문에 스크롤이 가능한 것이지, 말이 안되는 구조이기도 하다.

이런 상태에서 벗어나려고 노력햇으나, 당겨서 새로고침 문제가 재발하고,,,,, 해결이 되지 않는 이슈가 있었다.

 

그래서 결론은 ?

관리하는 다른 페이지들에서도 여러번 테스트를 거쳤는데, 이 Safari on IOS 의 Native 한 기능들을 사용하려면 대략 3가지 정도의 조건을 만족해야 동작하는것 같다.

1. 전체 페이지의 스크롤 길이가 <html /> <body /> 요소들에게 잡혀야 한다.

2. html 이나 body 태그에 style 속성이 아닌 전역 정적 css 트리 ( CSSOM ) 에 position 이나 overflow 등의 속성을 사용하면 안된다.
( ex : index.css 에 위의 body 부분 코드처럼 overflow : hidden 등을 사용하는 경우인데, css 파일에서 선언함을 의미합니다 )

3. 동적으로 js 를 이용해 dom 에 style 속성을 주입해서 overflow : hidden 등을 사용후, 원상 복귀 시켜야 할때
Element.style.removeProperty('overflow') 등으로 깔끔하게 지워주면 잠시동안만 Native 기능이 비활성화 되고, 다시 사용이 가능해진다. 

position : relative 등도 사용을 지양하는 것이 좋고, 가급적이면 브라우저가 DOM 을 파싱하는데 직접적인 영향을 끼치는 속성들은 사용하지 않는것이 바람직 하다.
( relative 를 사용해도 왠만한 Native 기능은 동작 합니다 )

전체화면 모드 주소창 인터렉션, status bar 터치 미작동, 당겨서 새로고침등은 Safari 브라우저 나름의 파싱 방식이 있고, 이 기능이 동작할지 안 할지의 기준또한 정해져 있는듯 하다.

현재까지는 앱처럼 따로 이런 기능들을 키고 끄는 방법을 찾지 못했고, 아마도 많은 웹 개발자들이 비슷한 문제를 겪고 있지 않을까 생각한다.
( 나만 모르나,,,? 방법을 아시는분이 공유 해주시면 너무 감사드립니다. )