【初心者向け】Vue.jsでモーダルを作成する方法をわかりやすく解説

vue-js-prograshi(プロぐらし)-kv Vue.js
記事内に広告が含まれていることがあります。
[PR]

Vue.jsを使うとクリックで操作できるおしゃれなモーダルを比較的簡単に作成することができます。

ここでは、Vue.jsを使ったモーダルの作成方法を一つ一つの機能や挙動を確認しながら解説しています。


作成するモーダル

次のようなモーダルを作成します。

ボタンをクリックするとモーダルが表示され、モーダル内の閉じるボタンをクリックすると滑らかに閉じます。


はじめに

Vue.jsを使ってテンプレートを作成する主な方法は、次の3つがあります。

  1. Vue単一ファイルコンポーネント(.vue)
  2. templateオプションの値に文字列として作成
  3. scriptタグのtype属性に”text/x-template”を指定しid名をつける

最も実用性が高いのは、拡張子が.vueのファイルを作成し、import fromで読み込んだあとに、componentsに登録する方法です。ただし、webpackでコンパイルするといった追加処理が必要になります。

No.3のscriptタグを使うと、通常のHTMLファイルで簡単にVue.jsをテストすることができます。

ここでは、このscriptタグを使った方法でVue.jsを作成しています。.vueファイルで使うときは、scriptタグの中身をvueファイルに移動してください。


モーダルの構成

モーダルは大きく2つのパーツからできています。

(1)モーダルを表示するボタンと、(2)モーダル内の表示です。

スタイルを外した場合に以下のようになります。

 ↓ ボタンをクリック

モーダルの表示・非表示の管理は、dataオプションに定義したshowModalという変数の値で管理します。

デフォルトをfalseにし、モーダルを表示するボタンをクリックするとtrueに、閉じるボタンをクリックするとfalseになる設定です。


ボタンの作成

まずは、「モーダルを表示する」ボタンを作成します。

ファイル名はindex.htmlとしていますが、.htmlであればファイル名は任意です

<!DOCTYPE html>
<html>
  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script> 
  </head>
  <body>
    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
    </div>

    <script>
      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>
  </body>
</html>

Vue.jsのCDNを読み込む

headタグの中身は以下のようになっています。

  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script> 
  </head>

Vue.jsを使うために、https://unpkg.com/vue からCDNとして読み込んでいます。

Vue.js自体をアプリケーションにインストールしている場合は、この記述は不要です。


Vueインスタンスの作成とコンポーネントの登録

Vue.jsを使う上で必須になるのが、Vueインスタンスの生成と、Vueコンポーネントの登録です。

それを行っているのが下部のscirptタグです。

    <script>
      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>

Vueインスタンスの生成

Vue.jsを使うためには、Vueインスタンスを生成する必要があります。

new VueでVueインスタンスを生成し、引数にオブジェクトを渡します。こうすることでVueインスタンスに固有の情報を持たせることができます。

new Vue({ })
tips

CDNでVue.jsのライブラリを読み込んだことで、Vueクラスが使えるようになっています。


コンポーネントの登録

Vueインスタンスを生成したときに、HTMLのどの部分がVueのコンポーネントなのかを指定する必要があります。

それが、引数のプロパティelです。elの値には#id名を指定します。こうすることで、そのid名をもつタグの配下をVueのコンポーネントとして使うことができます。

el: '#id名'

(参考)Vue.js コンポーネントの基本

tips

Vue単一コンポーネント(.vue)の場合は、そのファイル自体がVueのコンポーネントなので、elプロパティを設定する必要はありません。


dataの登録

Vue.jsではdataオプションにプロパティを定義することで、変数として使うことができます。

この変数はVueインスタンスに登録され、v-bindやv-modelを使うことでHTMLと連動させることができます。

      new Vue({
        (省略)
        data: {
          キー名1: 値1,
          キー名2: 値2,
          ・
          ・
          ・
        }
      });
tips

dataはプロパティではなく関数です。このためVue.jsではdataプロパティと呼びます。

Vue単一コンポーネント(.vue)の場合は、export defaultの中にdata(){ return{ } }として定義します。

(参考)Vue.js dataは関数


Vueコンポーネントの作成

Vue.jsを使う準備ができたので、Vueのコンポーネントをbodyタグの中に記述します。

    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
    </div>

divタグのid名を、Vueインスタンス生成時に指定した、elプロパティの値と合わせます。

@clickとは?

@clickを使うとクリックイベントを設定することができます。(@clickの@は、v-onの省略形です。)

@click="メソッド"

HTMLタグの属性に@(v-on)をつけると、その値はVue(JavaScript)の世界になります。

値にmethodプロパティで定義したメソッド名を入れれば、クリック時に発火します

@clickの値にはメソッドを指定する以外にも、式を入れることもできます

@click="式"

@click="showModal = true"とすれば、クリック時にdataオプションで定義したshowModal変数の値がtrueになります。


ブラウザの表示

現時点でのブラウザの表示は次のようになります。まだクリックしても何も変化しません。


テンプレートの作成と登録と呼び出し

ボタンをクリックしたときに表示するテンプレートを作成し、コンポーネントとして登録します。

<!DOCTYPE html>
<html>
  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script>
    
    <script type="text/x-template" id="modal-template">
      <div>
         モーダル内defaultタイトル
          <button @click="$emit('close')">閉じる</button>
      </div>
    </script>
    
  </head>
  <body>
    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
      <Modal v-if="showModal" @close="showModal = false" />
    </div>

    <script>
      Vue.component("Modal", {
        template: "#modal-template"
      });

      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>
  </body>
</html>

テンプレートの作成

Vue.jsにコンポーネントを登録する方法の1つに 「scriptタグのtype属性に”text/x-template”を指定しid名をつける」方法があります。

ここでは、headタグ内にscriptタグを設置してコンポーネントを作成しています。

    <script type="text/x-template" id="modal-template">
      <div>
         モーダル内defaultタイトル
          <button @click="$emit('close')">閉じる</button>
      </div>
    </script>

ここではテンプレートのid名をmodal-templateとしています。

tips

scriptタグを設置する場所はheadタグ以外にもbodyタグの中でも問題ありません。

HTMLとなる要素はheadタグに記述し先に読み込ませ、JavaScriptの処理はbody閉じタグの直前に配置するのが一般的です。


$emitとは?

$emitとは、子テンプレートから親のテンプレートにイベントを渡す方法です。

子テンプレートでclickイベントの値に、$emitを使ってイベント名を指定すると、その要素がクリックされたときに、親側の@イベント名が発火します。

@click="$emit('イベント名')"

親側のテンプレートでイベントを受け取るには@イベント名とします。

@イベント名="メソッド or 式"

ここではbuttonタグに@click="$emit('close')を設置しています。buttonタグがクリックされると、親側の@closeで指定しているイベントが発火します。

@close="showModal = false"なので、変数showModalの値がfalseになります。

Vue.jsの親子間のデータの受け渡し

Vue.jsにおいて親から子テンプレートにデータを渡すときはpropsを使います。

子から親テンプレートにイベントを渡すときは$emitを使います。

これを、「props down, events up」といいます。Vue.jsの重要な概念です。

(参考)Vue.js 親子間のやりとり


テンプレートをコンポーネントとして登録

作成したテンプレートを親となっているVue.jsの中でコンポーネントとして登録します。

scriptタグの中で、Vue.componentを定義します。

      Vue.component("テンプレート名", {
        template: "#id名"
      });

templateプロパティの値を、指定したテンプレート名として登録します。

Vue.componentの使い方(応用編)

Vue.componentの中で、data, method, watch, computed, mounted, propsなどのオプションを設定することもできます。(※elオプションなどルート固有のものは指定できません)

templateはid名以外にも、直接文字列として指定することもできます。

Vue.component('テンプレート名', {
  data(){
    return{
      count: 0
    },
  }
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

複数のテンプレートを登録したい場合は、Vue.componentを新たに登録します。

(参考)Vue.js コンポーネントの基本


Vue単一テンプレートの場合

Webpackを使って、.vueファイルをテンプレートとして読み込む場合は、import fromでテンプレートファイルを読み込み、export defaultの中にcomponentsオプションを定義して、コンポーネントとして登録します。

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}

(参考)Vue.js コンポーネントの登録


コンポーネントを呼び出す

テンプレートをコンポーネントとして登録したら、親のテンプレートの中でHTMLタグとして呼び出すことができます。

以下のタグを設置するのみです。

 <コンポーネント名 />

閉じタグを省略しています。上記は<コンポーネント名></コンポーネント名>と同じです。

ここでは、コンポーネント名をModalとしているので次のようになります。

<Modal v-if="showModal" @close="showModal = false" />

v-ifとは?

属性にv-ifが指定してあります。v-ifとは値がtrueの時はその要素を表示し、falseの時は非表示にする機能です。

ここでは、値に変数showModalを指定しています。

呼び出しているModalテンプレートの中で、@click=$emit('close')が発火すると、@close="showModal = false"が実行され、v-ifの値がfalseになるのでModalが非表示になります。


ブラウザの表示

 ↓ ボタンをクリック

 ↓ 「閉じる」ボタンをクリック


slotで親から子にデータを渡す

現状では、モーダル内に表示するタイトルが「モーダル内defaultタイトル」となっています。

ここに、親から渡したデータを表示します。

<!DOCTYPE html>
<html>
  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script>

    <script type="text/x-template" id="modal-template" >
      <div>
         <slot name="header">
             モーダル内defaultタイトル
         </slot>
         
          <button @click="$emit('close')">閉じる</button>
      </div>
    </script>
    
  </head>
  <body>
    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
      <Modal v-if="showModal" @close="showModal = false">
        <h3 slot="header">モーダル内のタイトル</h3>
      </Modal>
    </div>

    <script>
      Vue.component("Modal", {
        template: "#modal-template"
      });


      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>
  </body>
</html>

slotによる親から子へのデータ受け渡し

通常、テンプレートは使い回します。そのときに、テンプレートの中の要素を呼び出す親テンプレートに内容を変更したいときがあります。

その時は、slotタグとslot属性を使うことで、親で指定した要素を、子テンプレートに渡すことができます。

子テンプレートにslotタグを設置

親からのデータを受け取る場所として、slotタグを設置します。slotタグは複数設置することもあるため、name属性でslotタグに名前を付けます。

<slot name="スロット名">デフォルトのテキスト</slot>

親からデータが渡されない場合は、ここで指定している内容が表示されます。

親テンプレートから子のslotにデータを渡す

子のslotタグにデータを渡すには、渡したいタグの属性にslot="スロット名"をつけるだけです。

slot属性をつけたタグが丸ごと子テンプレートのslotタグと置き換わります。

<タグ slot="スロット名">内容</タグ>
tips

slotタグはコンパイル後は存在しないものとして扱われます。

親からデータが渡されていない場合のコンパイル例は次のようになります。

<div>
 <slot name="header">タイトル</slot>
</div>

↓ コンパイル後

<div>
 "タイトル"
</div>


ブラウザの表示

ブラウザの表示は次のようになります。

 ↓ ボタンをクリック

これまで、「モーダル内defaultタイトル」となっていた部分に、親テンプレートから渡されたh3タグが入りました。


モーダルの作成

大枠の要素が完成したので、つづいて、モーダルを表示したときに全面を黒色で覆うようにスタイルを調整します。

モーダルのレイアウトを整えるためには、以下の3つが必要です。

div 全体を覆う黒いマスク
 div コンテンツの位置調整
  div コンテンツのスタイル

ここでは、順にclass属性として、 modal-mask > modal-wrapper > modal-containerをつけます。

HTML

index.htmlを次のようにします。

<!DOCTYPE html>
<html>
  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script>
    <link rel="stylesheet" type="text/css" href="/style.css" />

    <script type="text/x-template" id="modal-template" >
      <div class="modal-mask">
        <div class="modal-wrapper">
          <div class="modal-container">
             <slot name="header">
                   モーダル内defaultタイトル
             </slot>
             <button @click="$emit('close')">閉じる</button>
          </div>
        </div>
      </div>
    </script>
    
  </head>
  <body>
    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
      <Modal v-if="showModal" @close="showModal = false">
        <h3 slot="header">モーダル内のタイトル</h3>
      </Modal>
    </div>

    <script>
      Vue.component("Modal", {
        template: "#modal-template"
      });


      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>
  </body>
</html>

コンポーネントとして呼び出しているscriptタグに、divタグを3つ配置しクラス名をつけています。

    <script type="text/x-template" id="modal-template" >
      <div class="modal-mask">
        <div class="modal-wrapper">
          <div class="modal-container">
             <slot name="header">
                   モーダル内defaultタイトル
             </slot>
             <button @click="$emit('close')">閉じる</button>
          </div>
        </div>
      </div>
    </script>

headタグ内でlinkタグを使ってスタイルシート(style.css)を読み込んでいます。

<link rel="stylesheet" type="text/css" href="/style.css" />


モーダルのスタイル

モーダルのスタイルをstyle.cssに記述します。

.modal-mask {
  position: fixed;
  z-index: 9999;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: table; 
}

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;
}

.modal-container {
  width: 300px;
  margin: 0px auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
}

モーダルのマスク

マスク部分のスタイルは次のようになります。

.modal-mask {
  position: fixed;
  z-index: 9999;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: table; 
}
z-index

z-indexプロパティは、要素の重なり順序を指定するものです。値が小さい(最小0)ほど一番手前に表示されます。

モーダルの黒い枠は一番後ろに表示したいため値を9999としています。

注意点

z-indexはpositionプロパティで、absolute, relative, fixedを指定したときのみ有効です。


position: fixed;

positionをfixedとすることで、画面右上を基準(0, 0)として要素を配置することができます。

top:0; , left:0; とすることで、画面を覆う黒い背景を、画面左上からスタートさせることができます。

width: 100%; height: 100%;とすることで、この要素が画面全体を覆います。

なお、背景色でrgbaを使うと、第4引数でopacityを指定することができます。

opacityとは?

opacityとは、「不透明度」のことです。値は0~1をとることができます。

  • 0 完全に透明(不透明でない=見ることができない)
  • 1 完全に不透明(透けない)

opacity: 0.5; だと、背面の要素が透けて見えます。


display: table;

display: table;は要素をtableタグのように扱えるプロパティと値です。

tableタグの中の要素にしたいものに、display: table-cell;をつけます。


コンテンツの位置調整

モーダルの中に表示するコンテンツの位置調整はmodal-wrapperで行います。

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;
}

親要素のmodal-maskに設定した display: table; の子要素とするため、display: table-cell;としています。(tableタグとtdタグのような関係になります)

セルの中の配置を指定するには、vertical-alignとtext-alignが便利です。

vertical-align: middle;とすれば、上下中央に配置できます。中のテキストの配置を指定したいときは text-alignを使います。


コンテンツのスタイル調整

最後に、文字を表示するコンテンツ部分のスタイルを調整しています。

.modal-container {
  width: 300px;
  margin: 0px auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
}

widthを指定しないと、横幅いっぱいにひろがってしまいます。


ブラウザの表示

「モーダルを表示する」ボタンをクリックすると、モーダルが表示されます。

これで、モーダルと呼べるものになりました。


アニメーション(トランジション)の設定

モーダルを閉じるときにフワっと閉じるアニメーションを追加します。

HTML

index.htmlのモーダルのテンプレートのタグの一番外側にtransitionタグを追加します。

<!DOCTYPE html>
<html>
  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script>
    <link rel="stylesheet" type="text/css" href="/style.css" />  
    
    <script type="text/x-template" id="modal-template" >
     <transition name="modal">
        <div class="modal-mask">
          <div class="modal-wrapper">
            <div class="modal-container">
               <slot name="header">
                     モーダル内defaultタイトル
               </slot>
               <button @click="$emit('close')">閉じる</button>
            </div>
          </div>
        </div>
      </transition>
    </script>
    
  </head>
  <body>
    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
      <Modal v-if="showModal" @close="showModal = false">
        <h3 slot="header">モーダル内のタイトル</h3>
      </Modal>
    </div>

    <script>
      Vue.component("Modal", {
        template: "#modal-template"
      });


      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>
  </body>
</html>


transitionタグとは?

transitionタグとはVue.jsのテンプレートの中で使うことで特別な意味をもつようになるタグです。トランジションやアニメーションをつけるときに使います。

v-ifやv-showが付与されているタグとあわせて使います。

v-ifやv-showにより要素が消える/表示する(遷移する)状況に合わせて以下の6つのクラスが付与されます。

クラス状態
v-enterenterの開始状態。要素が挿入される前に適用され、要素が挿入された 1 フレーム後に削除。
v-enter-activeenterの活性状態。遷移中に適用。
v-enter-toenterの終了状態。要素が挿入された 1 フレーム後に追加され、遷移が終了したら削除。v-enterと入れ替わりで付与される。
v-leaveleaveの開始状態。
v-leave-activeleaveの活性状態。
v-leave-toleaveの終了状態。

leaveはenterと逆向きの遷移中に適用されるクラスです。

https://jp.vuejs.org/v2/guide/transitions.html

transitionタグにname属性をつけると、付与されるクラス名のv-がname属性の値になります。

 <transition name="modal">

上記のように、name属性の値にmodalを指定すると以下のようになります。

クラス状態
modal-enterenterの開始状態
modal-enter-activeenterの活性状態
modal-enter-toenterの終了状態
modal-leaveleaveの開始状態
modal-leave-activeleaveの活性状態
modal-leave-toleaveの終了状態

(参考)Vue.js Enter/Leave とトランジション一覧

スタイル

style.cssを以下のようにします。

.modal-mask {
  position: fixed;
  z-index: 9999;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: table; 
  transition: opacity 1s ease;
}

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;

}

.modal-container {
  width: 300px;
  margin: 0px auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  transition: all 1s ease;
}


.modal-leave-active {
  opacity: 0;
}

.modal-leave-active .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}

transitionプロパティ

トランジションの対象となるmodal-maskとmodal-containerにtransitionプロパティをつけます。

transition: 対象プロパティ, 遷移時間, 遅延時間,  変化方法;

対象プロパティと時間の指定は必須です。並び順に指定はありません(先に記述された時間が遷移時間になります。)

transitionは次の4つのプロパティの値を一気に設定できる便利なプロパティです。

プロパティ名内容
transition-property対象プロパティ
transition-duration遷移にかける時間
transition-delay開始までの遅延時間
transition-timing-function変化方法

(参考)MDN CSS transition


modal-maskの変化

modal-maskに適用されるCSSは、transition: opacity 1s ease;.modal-leave-active {opacity: 0;}です。

閉じるボタンをクリックしたときに、opacityが1秒間かけて0へと変化していきます(消えていきます)。


modal-containerの変化

modal-containerに適用されるCSSは、 transition: all 1s ease;と、以下です。

.modal-leave-active .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}

modal-container内のすべての要素(all)が1秒間かけて、transformにより大きさ1.1倍になります。

ブラウザの表示

ブラウザの表示は次のようになります。



要素とレイアウトの調整

最後に、header以外に、子テンプレートの中にbodyとfooter用のslotを設置し、親のテンプレートからデータを渡せるようにします。

あとは、ボタンなどのスタイルを整えて完成です。

HTML

最終的なHTMLは以下になります。

<!DOCTYPE html>
<html>
  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script>
  <link rel="stylesheet" type="text/css" href="/style.css" />  
  
    <script type="text/x-template" id="modal-template">
      <transition name="modal">
        <div class="modal-mask">
          <div class="modal-wrapper">
            <div class="modal-container">

              <div class="modal-header">
                <slot name="header">
                  モーダル内defaultタイトル
                </slot>
              </div>

              <div class="modal-body">
                <slot name="body">
                  ●bodyコンテンツ
                </slot>
              </div>

              <div class="modal-footer">
                <slot name="footer">
                  ●footerコンテンツ
                  <button class="modal-default-button" @click="$emit('close')">
                    閉じる
                  </button>
                </slot>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </script>
    
  </head>
  <body>
    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
      <Modal v-if="showModal" @close="showModal = false">
         <h3 slot="header">モーダル内のタイトル</h3>
      </Modal>
    </div>

    <script>
      Vue.component("Modal", {
        template: "#modal-template"
      });

      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>
  </body>
</html>

スタイル

最終的なスタイルシートは以下になります。

.modal-mask {
  position: fixed;
  z-index: 9998;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: table;
  transition: opacity 0.3s ease;
}

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;
}

.modal-container {
  width: 300px;
  margin: 0px auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  transition: all 0.3s ease;
  font-family: Helvetica, Arial, sans-serif;
}

.modal-header h3 {
  margin-top: 0;
  color: #42b983;
}

.modal-body {
  margin: 20px 0;
}

.modal-default-button {
  float: right;
}


.modal-leave-active {
  opacity: 0;
}

.modal-leave-active .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}

以上で完成です。


補足:モーダル表示時もアニメーションさせる方法

上記で紹介したのは、閉じるときのみフワっと閉じるアニメーションを効かせましたが、開くときも同様のアニメーションを効かせることができます。

手順は次の2つです。

  1. transitionタグをModalコンポーネントを囲むように移動する。
  2. CSSにmodal-enterの処理を追加する。

transitionタグをModalコンポーネントを囲むように移動する

scriptタグで定義しているModalテンプレートにつけているtransitionタグを、Modalタグに移動します。

      <transition name="modal">
        <Modal v-if="showModal" @close="showModal = false">
           <h3 slot="header">モーダル内のタイトル</h3>
        </Modal>
      </transition>

CSSにmodal-enterの処理を追加する

トランジションの初期状態からの変化を指定するために、modal-enterの処理を2つ追加します。

.modal-enter{
  opacity: 0;
}

.modal-enter .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);

以上で完成です。

ブラウザの表示

表示と非表示の両方にフワっとしたアニメーションを適用することができました。

考察

transitionはv-ifやv-showに該当するイベントを検知して発火します。「モーダルを表示する」ボタンはModalテンプレートの外側にあるため、モーダルを開くときはイベントを検知できていないと推察されます。

v-ifがあるModalタグ自身を囲むことで、表示・非表示の両方でtransitionが発火すると考えられます。


全体のコード

HTML

<!DOCTYPE html>
<html>
  <head>
    <title>Modal Component</title>
    <script src="https://unpkg.com/vue"></script>
  <link rel="stylesheet" type="text/css" href="/style.css" />  
  
    <script type="text/x-template" id="modal-template">
        <div class="modal-mask">
          <div class="modal-wrapper">
            <div class="modal-container">
              <div class="modal-header">
                <slot name="header">
                  モーダル内defaultタイトル
                </slot>
              </div>

              <div class="modal-body">
                <slot name="body">
                  ●bodyコンテンツ
                </slot>
              </div>

              <div class="modal-footer">
                <slot name="footer">
                  ●footerコンテンツ
                  <button class="modal-default-button" @click="$emit('close')">
                    閉じる
                  </button>
                </slot>
              </div>
            </div>
          </div>
        </div>
    </script>
    
  </head>
  <body>
    <div id="app">
      <button id="show-modal" @click="showModal = true">モーダルを表示する</button>
      
      <transition name="modal">
        <Modal v-if="showModal" @close="showModal = false">
           <h3 slot="header">モーダル内のタイトル</h3>
        </Modal>
      </transition>
    </div>

    <script>
      Vue.component("Modal", {
        template: "#modal-template"
      });

      new Vue({
        el: "#app",
        data: {
          showModal: false
        }
      });
    </script>
  </body>
</html>

スタイル

.modal-mask {
  position: fixed;
  z-index: 9998;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: table;
  transition: opacity 0.3s ease;
}

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;
}

.modal-container {
  width: 300px;
  margin: 0px auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  transition: all 0.3s ease;
  font-family: Helvetica, Arial, sans-serif;
}

.modal-header h3 {
  margin-top: 0;
  color: #42b983;
}

.modal-body {
  margin: 20px 0;
}

.modal-default-button {
  float: right;
}

.modal-enter{
  opacity: 0;
}


.modal-leave-active {
  opacity: 0;
}

.modal-enter .modal-container, 
.modal-leave-active .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}


参考リンク

Vue.js モーダルコンポーネントの例

モーダル以外にも、Markdownエディタや、SVGグラフなど興味深い例がのっています。

タイトルとURLをコピーしました