Vue.jsを使うとクリックで操作できるおしゃれなモーダルを比較的簡単に作成することができます。
ここでは、Vue.jsを使ったモーダルの作成方法を一つ一つの機能や挙動を確認しながら解説しています。
作成するモーダル
次のようなモーダルを作成します。
ボタンをクリックするとモーダルが表示され、モーダル内の閉じるボタンをクリックすると滑らかに閉じます。
はじめに
Vue.jsを使ってテンプレートを作成する主な方法は、次の3つがあります。
- Vue単一ファイルコンポーネント(.vue)
- templateオプションの値に文字列として作成
- 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({ })
コンポーネントの登録
Vueインスタンスを生成したときに、HTMLのどの部分がVueのコンポーネントなのかを指定する必要があります。
それが、引数のプロパティelです。elの値には#id名
を指定します。こうすることで、そのid名をもつタグの配下をVueのコンポーネントとして使うことができます。
el: '#id名'
dataの登録
Vue.jsではdataオプションにプロパティを定義することで、変数として使うことができます。
この変数はVueインスタンスに登録され、v-bindやv-modelを使うことでHTMLと連動させることができます。
new Vue({
(省略)
data: {
キー名1: 値1,
キー名2: 値2,
・
・
・
}
});
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としています。
$emitとは?
$emitとは、子テンプレートから親のテンプレートにイベントを渡す方法です。
子テンプレートでclickイベントの値に、$emit
を使ってイベント名を指定すると、その要素がクリックされたときに、親側の@イベント名
が発火します。
@click="$emit('イベント名')"
親側のテンプレートでイベントを受け取るには@イベント名
とします。
@イベント名="メソッド or 式"
ここではbuttonタグに@click="$emit('close')
を設置しています。buttonタグがクリックされると、親側の@close
で指定しているイベントが発火します。
@close="showModal = false"
なので、変数showModalの値がfalseになります。
テンプレートをコンポーネントとして登録
作成したテンプレートを親となっているVue.jsの中でコンポーネントとして登録します。
scriptタグの中で、Vue.componentを定義します。
Vue.component("テンプレート名", {
template: "#id名"
});
templateプロパティの値を、指定したテンプレート名として登録します。
コンポーネントを呼び出す
テンプレートをコンポーネントとして登録したら、親のテンプレートの中で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="スロット名">内容</タグ>
ブラウザの表示
ブラウザの表示は次のようになります。
↓ ボタンをクリック
これまで、「モーダル内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としています。
position: fixed;
positionをfixedとすることで、画面右上を基準(0, 0)として要素を配置することができます。
top:0;
, left:0;
とすることで、画面を覆う黒い背景を、画面左上からスタートさせることができます。
width: 100%; height: 100%;
とすることで、この要素が画面全体を覆います。
なお、背景色でrgbaを使うと、第4引数でopacityを指定することができます。
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-enter | enterの開始状態。要素が挿入される前に適用され、要素が挿入された 1 フレーム後に削除。 |
v-enter-active | enterの活性状態。遷移中に適用。 |
v-enter-to | enterの終了状態。要素が挿入された 1 フレーム後に追加され、遷移が終了したら削除。v-enterと入れ替わりで付与される。 |
v-leave | leaveの開始状態。 |
v-leave-active | leaveの活性状態。 |
v-leave-to | leaveの終了状態。 |
leaveはenterと逆向きの遷移中に適用されるクラスです。
transitionタグにname属性をつけると、付与されるクラス名のv-
がname属性の値になります。
<transition name="modal">
上記のように、name属性の値にmodalを指定すると以下のようになります。
クラス | 状態 |
---|---|
modal-enter | enterの開始状態 |
modal-enter-active | enterの活性状態 |
modal-enter-to | enterの終了状態 |
modal-leave | leaveの開始状態 |
modal-leave-active | leaveの活性状態 |
modal-leave-to | leaveの終了状態 |
(参考)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 | 変化方法 |
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つです。
- transitionタグをModalコンポーネントを囲むように移動する。
- 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);
}
参考リンク
モーダル以外にも、Markdownエディタや、SVGグラフなど興味深い例がのっています。