본문 바로가기
펀잇

[🐛트러블슈팅] 토스트를 구웠는데 먹을 수 없다구요?!?!

by 해-온 2023. 10. 19.
dialog에서 토스트가 가려져요....

 

 

'펀잇'에서 리뷰를 작성할 때 각종 알람이 뜬다.

 

예를 들어 사용자가 이미지를 등록한다고 생각해 보자.

5MB 이하의 이미지만 받고 있다고 할 때 그보다 큰 용량의 이미지가 들어오면 알림을 띄운다.

 

그동안은 alert를 통해 알림을 주고 있었는데 토스트가 완성되면서 이를 갈아 끼우기로 했다.

 

그런데 토스트가 보이지 않는다.

처음에 렌더링 자체가 되지 않는 오류인가 했는데 개발자 도구의 Layers 탭을 켜서 보니 (빨간색 주목)

 

dialog 뒤에 숨겨져 있는 것을 볼 수 있다.

토스트의 z-index가 dialog의 z-index보다 훨씬 높은데도 가려진다.

(important까지 줘봤다...)

 

왜 이런 문제가 일어났는지 해결 과정을 따라가 보자.

 

1. 쌓임 맥락(Stacking Context)

쌓임 맥락(Stacking Context)은 HTML 요소들이 z축에서 어떻게 쌓이는지 결정한다.

즉, 어떤 요소가 다른 요소 위에 표시되어야 하는지 결정하는 데 사용되는 것이다.

 

특정 요소의 렌더링 순서는 자신의 z-index 속성 값에 영향을 받는다.

z-index 속성을 적용하려면 아래 조건을 만족하면 된다.

 

  1. 문서의 루트 요소 (html)
  2. position이 absolute , relative이며, z-index가 auto가 아닌 요소
  3. position이 fixed , sticky 인 요소
  4. flexbox 컨테이너의 자식 중 z-index가 auto가 아닌 요소
  5. grid 컨테이너의 자식 중 z-index가 auto가 아닌 요소
  6. opacity가 1보다 작은 요소

 

자식 요소의 z-index 값은 부모 요소 내에서만 유효하다.

따라서 토스트의 z-index가 적용되지 않는 이유가 부모 요소가 달라서가 아닐까 생각해 보았다.

 

당시 Portal을 통해 토스트는 body 내부의 컨테이너에, dialog는 body 직접 붙이고 있었다.

  <body>
    <div id="root"></div>
    <div id="toast-container"></div>
    <dialog></dialog>
  </body>

 

 

사실상 body라는 부모 안에 들어있는 것은 맞지만,

토스트는 껍데기가 있기 때문에 혹시나 다르다고 인식될까 봐 dialog도 div로 감싸주기로 했다.

 

  <body>
    <div id="root"></div>
    <div id="toast-container"></div>
    <div id="dialog-container"></div>
  </body>

 

하지만 이렇게 해도 z-index는 적용되지 않고, 여전히 dialog가 상위 레이어에 위치한다.

 

검색을 해보다가 일반적으로 z-index가 적용되지 않을 때는 html 순서에 따라 레이어가 쌓인다는 말을 보고

toast-container와 dialog-container의 순서를 바꿔보기로 했다.

 

  <body>
    <div id="root"></div>
    <div id="dialog-container"></div>
    <div id="toast-container"></div>
  </body>

 

그래도 여전히 적용되지 않는다.

 

앞서 이야기했던 z-index 속성을 적용하려면 만족되어야 하는 조건을 각 컨테이너에 주어도 역시 적용되지 않는다.

다른 방법을 생각해 보자.

 

2. Popover API

토스트의 문제인가 싶어 토스트 react 라이브러리인 toastify를 사용해 보기로 했다.

하지만 또 dialog에 가려 보이지 않는다.

 

 

toasify의 issue를 살펴보자.

역시 똑같은 오류를 발견한 사람들이 있다.

 

열려있는 issue를 보자.

글쓴이는 Popover API를 통해 상위 레이어에 위치시키는 것에 대해 말하고 있다.

dialog 이야기를 하는 것을 보니 나와 똑같은 문제를 겪었나 보다...

 

Popover란 아래 사진과 같이 메뉴와 토글 팁 등 추가 정보를 제공하는 컴포넌트를 지칭하는 말이다.

Popover API를 이용하면 이 Popover를 별도의 라이브러리 없이 웹 API로 구현할 수 있다.

Popover API의 가장 큰 특징을 보면 z-index를 사용하지 않고도 상위 레이어로 인식할 수 있다는 것이다.

 

그렇다면 우리는 Popover API를 사용해 토스트를 구현할 수 있지 않을까?

Typical use cases for the popover API include user-interactive elements like action menus, custom "toast" notifications, form element suggestions, content pickers, or teaching UI.

mdn문서에서도 Popover의 사용 예시로 toast를 말하고 있는데 이를 통해 상위 레이어로 올릴 수 있지 않을까?

 

하지만 그전에 우리는 이 신기술이 브라우저에 적용될 수 있는지에 대해 알아봐야 한다.

dialog조차도 모든 브라우저에 지원된다고 했지만 오랫동안 업데이트를 하지 않은 사파리 유저에게 오류가 있었듯

이 Popover API는 우리가 선정한 브라우저 지원 범위에 해당하는지 살펴보아야 한다.

 

 

앗, 2023년 6월에 출시된 API라 그런지 역시 지원되지 않는 브라우저가 많다.

특히 한국 유저가 '주'이고, 모바일 서비스인 '펀잇'에 Samsung Internet 브라우저에 지원되지 않는 것은 치명적이다.

그뿐만 아니라 Safari에서는 비교적 최신 버전인 16.6 버전(23년 6월)까지 지원되지 않는다.

 

만약 Popover API를 적용한다고 하더라도 아예 보이지 않으면 적용한 의미가 없다.

따라서 사용하지 않기로 결정했다.

다른 방법을 찾아보자.

 

3. div role="dialog"

다시 toastify의 issue를 보다가 이런 댓글을 보게 되었다.

Ariakit을 사용해서 Toastify가 작동하는 예시를 만들었다고 한다.

 

해당 링크로 들어가 예시를 보니 

dialog를 열었음에도 토스트가 가려지지 않고 잘 나오는 것을 볼 수 있다.

 

Layers 탭을 켜서 봐도 토스트가 제일 최상단에 위치해 있다.

이 Ariakit을 다운로드해 해당 dialog를 내가 만든 토스트에 붙여보았을 때도

위 예시와 같이 토스트가 dialog 위에 뜨는 것을 볼 수 있었다.

(+ 위 그림에서 dialog는 녹색의 선이다)

 

그렇다면 왜 이 dialog는 어떤 차이가 있을까?

 

바로 role='dialog'라는 차이가 있었다.

내가 디자인 시스템을 통해 배포한 dialog는 HTML 태그 자체의 dialog였다.

<dialog>...</dialog>

 

그런데 Ariakit의 dialog는 div에 role을 dialog로 준 것이었다.

<div role='dialog'>...</div>

 

내가 사용한 dialog도 똑같이 div 태그로 바꾸고 role을 dialog로 주었을 때 토스트가 제대로 작동하는 것을 볼 수 있었다.

 

Layers도 토스트가 최상단에 나오는 것을 확인할 수 있다.

 

 

크루들과 한참 논의한 결과, 추측이지만 dialog는 항상 최상단에 위치하는 것 같다.

div로 바꾸었을 때 구조는 그대로였는데 Layer의 위치가 바뀐 것을 보면 말이다.

 

열심히 해결해 보고, 혹시 다른 팀의 팀원들도 해당 문제를 겪은 적이 있는지 물어봤는데

다들 나와 비슷한 경험이 있었고, div로 바꾸어 해결했다고 했다.

 

dialog를 잘 쓰다가 div로 바꿔야 하니 어느 정도의 찝찝함은 있었다.

또, dialog를 쓰기 위해 ref를 걸어 showModal도 다 해줬는데

상태를 사용하는 로직으로 돌려야 하는 것이 참 아까웠다.

 

웹 접근성적인 측면에서 dialog를 사용하다가 div role='dialog'로 바꾸었을 때 저하되는 요소가 없을지 많은 고민을 했다.

(지금 찾은 것으로는 ESC로 닫을 수 없다는 것, 또 back drop이 없어 직접 만들어주어야 하는 것이 있다)

 

그런데 dialog 태그를 사용하기 위해 토스트 대신 alert를 그대로 유지하고

사용자에게 해당 alert를 매번 꺼야 하는 동작을 하게 만든다면 

그게 접근성 측면에서 좋은 것일까 생각이 들었다.

 

또, 디자인적인 측면에서도 어느 곳에는 토스트, 어느 곳에는 alert로 한다면 전체 통일성이 무너지지는 않을까?

내 토스트... 열심히 구웠는데 먹을 수 없다니 말도 안 된다....🥪

 

이런저런 이유를 생각해 보았을 때 결국 div로 바꾼 것에 만족한다!

그래도 dialog 태그를 사용했을 때 최상단으로 올라가지 않는 방법을 알고 싶다...

알려줘요.

 

끗-

 

댓글