본문으로 건너뛰기
개발 머꼬
개발 노트Vue
hohyeon.dev16

카드 안의 모달이 position: fixed인데도 화면을 못 덮은 이유

  • #CSS
  • #Engineering Note
  • #Vue

문제 발생

카드 안에 넣은 모달이 카드 밖으로 나오지 못했습니다. position: fixed를 줬는데도 화면 전체를 덮지 않았고, z-index를 아무리 올려도 옆 요소에 가렸습니다.

<div class="card">      <!-- transform: translateY(...) 애니메이션이 걸려 있음 -->
  <Modal v-if="open" />
</div>

원인 분석

DOM 위치가 문제입니다. Vue 공식 문서가 이 상황을 그대로 설명합니다 — 컴포넌트 템플릿의 일부가 논리적으로는 그 컴포넌트에 속하지만, 시각적으로는 DOM의 다른 곳에(심지어 Vue 앱 바깥에) 있어야 하는 경우가 있습니다.

모달이 대표적이고, 이유가 둘입니다.

position: fixed가 깨집니다. 조상에 transform이 걸려 있으면 그 조상이 새 컨테이닝 블록이 되어, fixed가 뷰포트가 아니라 그 조상을 기준으로 잡힙니다. 문서도 조상 <div class="outer">에 CSS transform 애니메이션을 걸면 모달 레이아웃이 깨진다고 지적합니다.

z-index가 갇힙니다. 문서의 표현대로 모달의 z-index자기를 감싼 요소들에 의해 제한됩니다. 바깥에 더 높은 z-index를 가진 요소가 있으면 모달을 덮습니다.

<Teleport>는 렌더 결과만 다른 곳으로 옮깁니다. 문서가 분명히 합니다 — <Teleport>는 렌더링되는 DOM 구조만 바꾸며 컴포넌트의 논리적 계층에는 영향을 주지 않습니다. props, 이벤트, provide/inject가 그대로 동작합니다.

해결 방안

  1. to로 목적지를 지정합니다.
<button @click="open = true">열기</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>모달 내용</p>
    <button @click="open = false">닫기</button>
  </div>
</Teleport>
  1. 목적지는 마운트 시점에 이미 있어야 합니다. 문서가 못박습니다 — to 대상은 <Teleport> 컴포넌트가 마운트될 때 이미 DOM에 있어야 합니다. <div id="modals">를 Vue가 나중에 그린다면 그 시점에는 없습니다. Vue 3.5+의 defer prop이 이 경우를 위해 대상 해석을 뒤로 미룹니다.
<Teleport defer to="#modals">...</Teleport>
  1. 조건부로 끄려면 disabled입니다. 데스크톱에서는 body로 보내고 모바일에서는 제자리에 두는 식으로 쓸 수 있습니다.
<Teleport :disabled="isMobile">...</Teleport>
  1. 같은 대상에 여러 개를 보내도 됩니다. 문서의 설명대로 순서는 단순 append여서, 나중에 마운트된 것이 뒤에 붙습니다.

  2. v-if<Teleport> 안쪽에 둡니다. 바깥에 두면 조건이 참이 될 때마다 텔레포트가 새로 마운트되고, 그때 목적지가 없을 수 있습니다.

  3. 접근성은 따로 챙깁니다. DOM 위치가 바뀌었을 뿐 포커스 트랩, aria-modal, Escape 처리는 자동으로 생기지 않습니다. 네이티브 <dialog>를 함께 검토합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.