vue로 모달 구현하기

2023-08-02


2023-03-09에 작성된 원문을 수정한 버전입니다

웹사이트를 이용하다 보면 모달창을 마주하게 된다. 일반적인 모달은 화면이 어두워지면서 기존의 화면은 fade out되면서, 화면 한가운데에서 focus되는 alert창과 같은 형태이다. 이를 vue로 어떻게 구현하는지 살펴보자.

모달 구현

vue로 모달을 구현하는 방법에는 여러가지가 있을 수 있다. 가장 간단한 형태는 하나의 파일 안에 모달을 다 집어넣는 방식일 것이다.

// MyModal.vue <template> <div> <p>Hello World</p> <!-- Modal --> <div id="modal"> <div class="modal"> <div class="modal-content"> <p>This is Modal</p> </div> </div> </div> </div> </template> <style> @import url('.../modal.css') </style> /* modal.css */ .modal { position: fixed; z-index: 10; inset: 0; background-color: rgba(0, 0, 0, 0.5); } .modal .modal-content { position: absolute; width: 600px; height: 400px; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; }

이 방식의 경우 아래 사진과 같이 <div id="modal">태그는 기존 화면이 위치하고 있는 DOM노드 하위에 종속될 수 밖에 없다.

vue modal 1

여기서 한 가지 아쉬운 점이 생긴다. modal 창은 기존의 화면과 구분되는 속성인데 DOM 노드상에서도 이를 구분할 수 없을까? 즉 <div id="modal"><div id="app">과 같은 레벨로 끌어올릴 수 없을까? 하는 생각을 품게된다.

teleport

이를 해결하기 위해 vue에서 제공하는 teleport 기능을 적용해보자. teleport공식문서상에서는

컴포넌트 템플릿의 일부를 해당 컴포넌트의 DOM 계층 외부의 DOM 노드로 "이동"할 수 있게 해주는 빌트인 컴포넌트입니다.

라고 설명하고 있다. 간단하게 요약하자면 기존에 보여지고 있던 화면과 논리적/기능적으로 구분된 또다른 화면을 표시해야할 때, 기존의 DOM 노드가 아닌 별도의 DOM노드로 마치 teleport(순간이동)할 수 있는 기능이다.

이를 사용하기 위해서는 일단 DOM이 그려지는 index.html안에 <div id="modal">이라는 새로운 태그를 추가한다. (id 대신에 class를 사용하더라도 작동은 정상적으로 하지만, 중복 문제를 피하기 위해 관습적으로 id를 사용하는 듯 하다.)

<!-- index.html --> <html> <head> ... </head> <body> <noscript> ... </noscript> <div id="app"></div> <div id="modal"></div> </body> </html>

그리고 기존에 있던 MyModal.vue에서는 <div id="modal">한 겹을 벗겨내고 대신에 <teleport to="#modal">을 넣어주면 된다.

<template> <div> <p>Hello World</p> <!-- Modal --> <teleport to="#modal"> <div class="modal"> <div class="modal-content"> <p>This is Modal</p> </div> </div> </teleport> </div> </template>

그러면 <div id="modal"><div id="id">와 DOM상에서 같은 레벨에 생성된 것을 확인할 수 있다.

vue modal 2

모달 열고 닫기

그렇다면 이제 이 모달을 동적으로 열고 닫을 수 있어야 한다. 방법은 예상하다시피 v-ifv-show를 사용하면 된다. 하지만 사용 방식에는 조금 차이가 있다.

<teleport>에는 오직 v-if만 사용할 수 있고, v-show는 작동하지 않는다. v-show를 사용하기 위해서는 <teleport>하위의 최상단 root노드에 걸어야만 작동한다.

v-if는 값이 false일 경우 렌더링 시점에서 아예 제외되므로 <teleport>에 걸면 하위의 모든 노드들이 렌더링되지 않지만, v-show의 경우에는 일단 렌더링하고나서 값에 따라 display:none으로 처리하는데 <teleport>html 태그가 아닌 vue에서 제공하는 기능에 불과하므로 display:none이 걸리지 않는다.

<template> <div> <p>Hello World</p> <button @click="isModalOpen = true">open</button> <!-- Modal --> <teleport to="#modal" v-if="isModalOpen"> <div class="modal"> <div class="modal-content"> <p>This is Modal</p> <button @click="isModalOpen = false">close</button> </div> </div> </teleport> </div> </template> <script setup> import { ref } from 'vue'; const isModalOpen = ref(false); </script>

Profile picture

하주헌 Neon

개발 관련 내용들과 일상에서 느끼는 점들을 남기고 있어요. 흔하게 널린 글보다는 나만 쓸 수 있는 글을 남기려 하고있어요.

Loading script...