전체 화면 모드, 왜 UI가 사라졌을까?fullscreen API

M_yeon·2025년 5월 5일
3

React

목록 보기
25/25
post-thumbnail

대시보드 페이지에서 사용자가 차트나 중요한 데이터에 집중할 수 있도록 뷰포트 전체를 활용하게 해주는 전체화면 기능이 있었습니다.

하지만 전체 화면 전환 후 차트에 마우스를 올렸을 때 나타나야 할 툴팁이나,
특정 상호작용으로 인해 노출되어야 할 UI 요소들이 나타나지 않는 문제가 발생...
마치 단순히 콘텐츠를 확대해서 보여주는 '읽기 전용(Read-only)' 모드처럼 동작했습니다.

이 문제를 해결하기 위한 방법

  1. 사용자는 전체 화면을 읽기만 가능하게 한다.
  2. 전체 화면에서도 기존의 모든 상호작용 기능을 그대로 유지한다.
    저는 당연히 사용자 경험을 위해 2번 방식을 구현하고 싶었어요 당연하게도

문제 해결을 위한 여정

처음에는 이 문제가 단순히 CSS의 쌓임 순서 문제일 것이라 생각했고
전체 화면이 된 차트 컴포넌트의 z-index 값을 낮추면 다른 UI 요소들이 알아서 위로 올라와 보일 것이라 예상했죠. 하지만 z-index만으로는 전혀 해결되지 않았습니다.

혹시 사용하고 있는 라이브러리의 문제일까 싶어 라이브러리 코드를 살펴보았습니다.
하지만 딱히 상호작용을 막거나 하는 코드는 없었고, 해당 라이브러리의 업데이트가 몇 년 전 마지막이었기에, 혹시 구형 라이브러리의 문제일까 싶어 다른 최신 라이브러리로 교체하여 구현해보고, 심지어 다른 라이브러리에서 전체 화면 관련 훅(Hook)만 따로 빼와 적용해보는 시도도 했지만 모조리 실~패!

그래서 아 이건 원초적인곳에 문제가 있을것이다라는 생각만 들었고

전체화면이 된 후에 top-layer라는 태그가 붙으며 fullscreen-enabled가 생기니 이걸 위주로 찾아봤습니다.
찾아보니 원인이 특정 라이브러리의 이슈가 아니라, 웹 표준인 Fullscreen API 라는게 있네요.

Fullscreen API를 통해 전체 화면으로 전환된 요소는 브라우저에 의해 top-layer라는 특별한 렌더링 계층으로 이동합니다. 이 top-layer는 일반적인 DOM 요소들이 쌓이는 기본 컨텍스트와 분리되어, 뷰포트 전체를 덮으며 페이지의 다른 모든 콘텐츠 위에 렌더링됩니다.

에서 보이는 것처럼, 개발자 도구로 전체 화면 요소를 검사하면 해당 요소가 일반적인 DOM 트리와는 별개로 top-layer라는 표시와 함께 최상위 계층에 위치하는 것을 확인할 수 있습니다.

바로 이 top-layer의 특성 때문에 z-index와 같은 일반적인 CSS 쌓임 맥락 속성들이 무력화되었던 것이더라고요.
top-layer에 있는 요소는 z-index 값에 관계없이 항상 가장 위에 그려지므로, 다른 DOM 요소들이 아무리 z-index를 높게 설정하더라도 전체 화면 요소 위에 표시될 수 없었습니다.

Top Layer와 가상 요소(Pseudo-elements)
처음에는 이 top-layer라는 개념이 ::before, ::after처럼 DOM 트리에 직접 나타나지 않는 부분을 선택하는 가상 요소 선택자(Pseudo-elements)와 어떤 관련이 있을까 했는데, top-layer는 요소를 선택하는 선택자의 개념이 아니라, 특정 조건을 만족하는 DOM 요소가 배치되는 렌더링 계층을 의미하는 것!

다만, 어떤 요소가 top-layer로 이동하는 '특정 조건' 중 하나가 해당 요소에 :fullscreen이라는 가상 클래스(Pseudo-class)가 적용되는 상태라는 것을 알게 되었습니다. :fullscreen 가상 클래스는 현재 전체 화면 모드인 요소를 선택하며, 이 요소는 top-layer에 위치합니다.

레이어 자체를 최상위로 올려버리고, css는 무력화 된다고 하니 react-portal로 하면 해결될것 같네?
fullscreen도 레이어를 별도로 깔아버리니 portal로 나도 별도로 레이아웃에 올려 버리면 되겠네 이렇게 하면 top-layer의 쌓임 맥락에 영향을 받지 않고 항상 가장 위에 표시될 것이라 예상했죠..

이를 위해 레이아웃 파일에

요소를 추가하고, 아래와 같이 간단한 Portal 컴포넌트를 만들어 전체 화면 플래그에 따라 상호작용 요소들을 Portal 내부에서 렌더링하도록 분기 처리했습니다.

Layout File

    <html lang="ko">
      <body>
        <div id="portal" />
          ...
      </body>
    </html>

Portal File

'use client';

import { useEffect, useState } from 'react';
import reactDom from 'react-dom';

type Props = {
  children: React.ReactNode;
};

export default function Portal({ children }: Props) {
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  if (typeof window === 'undefined') return <></>;
  if (!isMounted) return <></>;

  const node = document.getElementById('portal');
  return reactDom.createPortal(children, node);
}

이 Portal 컴포넌트를 활용하여 처음에는 제가 바라던 대로 전체 화면 상태에서도 모든 상호작용 요소들이 정상적으로 나타나는 것을 확인했지만요 문제가 많이 발생했습니다.

  1. ESC 키로 전체 화면 종료가 작동하지 않음: Portal로 렌더링된 요소에 포커스가 가거나 이벤트 처리가 꼬이면서 브라우저의 기본 전체 화면 종료 단축키(ESC)가 무력화되었습니다.

  2. F11 키 전체 화면 토글 문제: 마찬가지로 F11 키를 통한 전체 화면 진입/종료 토글 기능에도 문제가 발생했습니다.

  3. 기존 레이어 사라짐 문제: 전체 화면 API는 기본적으로 기존 콘텐츠 레이어를 유지한 채 해당 요소만 top-layer로 올리는 방식입니다. 하지만 Portal 분기 처리를 하면서 전체 화면 상태일 때 기존 레이어는 사라지고 Portal로 띄운 요소만 보이게 되는 문제가 발생했습니다.

이러한 여러 버그와 예상치 못한 동작들 때문에 결국 Portal을 활용한 해결 방안은 현재로서는 부적합하다고 판단했습니다.

많은 시도를 해보았지만 여러 버그가 발생했고, 제한된 시간 안에 안정적인 기능을 제공해야 했기에 아쉽지만 현재는 전체 화면 전환 시 사용자에게 해당 모드에서는 읽기만 가능하다는 점을 인지시키는 방향으로 기능을 변경하여 배포했습니다.

하지만 근본적인 해결책을 찾지 못한 찝찝함이 계속 남아있어 이것저것 시도 해보고 있는데요.
좋은 의견이나 해결 방안이 있다면 알려 주세요!

1개의 댓글

comment-user-thumbnail
2025년 5월 18일

top-layer라는 게 있군요 처음 들었습니다!

글 또한 흥미진진하게 잘 읽었습니다.
감사합니다

답글 달기