🤔

Vue でモーダルを作る:通常モーダルと Teleport モーダルの比較

に公開

Vue.js を使ったモーダルの実装には、DOMの構造内に配置する「通常モーダル」と、<body>直下に表示する「Teleportモーダル」の2通りのアプローチがあります。
それぞれの実装方法や特徴、使い分けのポイントを整理しながら、実際のコード例とともにご紹介します。
自分用メモとしてもまとめておきたい。

通常モーダルと Teleportモーダルとは?

「通常モーダル」は、そのままコンポーネントのDOMツリーの中にモーダルの要素を置く方法です。この方法はシンプルでわかりやすい反面、親要素のスタイルやレイアウトの影響を受けやすく、重ね順や位置がうまくいかないこともあります。経験上結構あれ?他の要素がバッティングしてるかも。となったりする。

一方、「Teleportモーダル」はVueのTeleportという機能を使って、モーダルの要素をDOMの<body>直下など指定した場所に移動して表示します。これにより親要素の影響を受けず、画面中央や最前面にモーダルを表示しやすくなります。

実装例

以下のコードでは、通常のモーダルと Teleport を用いたモーダルをそれぞれボタンで制御しています。

<template>
  <section class="section">
    <h1>モーダル</h1>
    <div>
      <button @click="toggleModal">モーダル開く</button>
      <div v-if="isOpen" class="modal" @click.self="toggleModal">
        <div class="modal__inner">
          <p>普通のモーダル!</p>
          <button @click="toggleModal">閉じる</button>
        </div>
      </div>
    </div>
    <div>
      <button @click="toggleTeleportModal">Teleportモーダル開く</button>
      <Teleport to="body">
        <div v-if="isTeleportOpen" class="modal" @click.self="toggleTeleportModal">
          <div class="modal__inner">
            <p>Teleportで作ったモーダル!</p>
            <button @click="toggleTeleportModal">閉じる</button>
          </div>
        </div>
      </Teleport>
    </div>
  </section>
</template>

<script setup lang="ts">
import { ref } from 'vue';
const isOpen = ref(false);
const isTeleportOpen = ref(false);

const toggleModal = () => {
  isOpen.value = !isOpen.value;
};
const toggleTeleportModal = () => {
  isTeleportOpen.value = !isTeleportOpen.value;
};
</script>

<style lang="scss" scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9999;

  &__inner {
    background: #fff;
    color: #222;
    padding: 2rem;
    border-radius: 1rem;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
    max-width: 500px;
    width: 90%;
    position: relative;
  }
}
</style>

.self 修飾子について

@click.self は、その要素自身がクリックされたときだけイベントを発火させる Vue の修飾子です。
モーダル背景(.modal)をクリックしたときにモーダルを閉じたいけど、モーダルの中身をクリックしたときには閉じたくない、という場合に便利です。

<div class="modal" @click.self="toggleModal">
  <!-- モーダル内 -->
</div>

動作確認

https://ocotank.github.io/daily-practice/#/modal/

まとめ

通常モーダルはシンプルに実装可能で、DOM構造との親和性が高いですね。
画像の上とかにモーダル出したい時は普通の方がいいと思う。
そのほかはTeleportモーダルで事足りてしまいそう。とっても楽でいいです。

おすすめの使い分け自分メモ

こんなときに使う!
親要素の影響を受けたくない Teleportモーダル
セクション内で使いたい 通常モーダル

Discussion