普通にモーダルを作るとなると、同じようなhtmlが羅列し煩わしいですが、Vueで作るとコンポーネント1つでと回せるので見通しが楽になります。
今回は閉じるボタンと背景をクリックして閉じるモーダルを作成してみます。
親コンポーネントを作成する
まず、親のコンポーネントを作成します。
親にはモーダルを開くためのボタンと文、そしてモーダルの中身を記述します。
dataにitemの配列をいれてそこにモーダルの中身を記述します。またv-show
で表示を切り替えするためにのフラグshowFlagと、該当のモーダルを入れるためのmodalItemを定義しておきます。
<template>
<div class="About">
<h1 class="text-3xl py-8 px-4 bg-gray-400">About</h1>
<div class="max-w-screen-xl mx-auto mt-10 px-4">
<div>
<p class="text-base">このボタンをクリックすると、画像つきのモーダルが開きます。</p>
<div class="mt-6"><button @click="openModal(item,0)" class="btn">モーダルを開く</button></div>
</div>
<div class="mt-8">
<p class="text-base">このボタンをクリックすると、テキストだけのモーダルが開きます。</p>
<div class="mt-6"><button @click="openModal(item,1)" class="btn">モーダルを開く</button></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'About',
data(){
return {
showFlag: false,
modalItem: '',
item: [
{
title: 'テキストだけのモーダル01',
text: 'サンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキスト',
img: 'slide01.jpg'
},
{
title: 'テキストだけのモーダル02',
text: 'サンプルテキストサンプルテキスト',
img: ''
}
]
}
},
methods: {
openModal (item,modalNumber){
this.showFlag = true;
this.modalItem = item[modalNumber];
},
closeModal(){
this.showFlag = false;
},
}
}
</script>
buttonに設定したクリックイベント:openModalには2つの引数が設定されており、1つはdataにあるitemの配列の取得、もう一つはモーダルのデータの配列番号をいれてます。クリックすることで、dataに定義されたmodalItemに該当のモーダルのデータを格納しました。また、同時に定義したshowFlagの真偽値をtrue
に変更しています。
このデータを子コンポーネントであるModal.vue
に送ります。送るためには子コンポーネントの読み込みと、そこにv-bind
で送りたいデータをバインディングします。
<template>
<div class="About">
<h1 class="text-3xl py-8 px-4 bg-gray-400">About</h1>
<div class="max-w-screen-xl mx-auto mt-10 px-4">
<div>
<p class="text-base">このボタンをクリックすると、画像つきのモーダルが開きます。</p>
<div class="mt-6"><button @click="openModal(item,0)" class="btn">モーダルを開く</button></div>
</div>
<div class="mt-8">
<p class="text-base">このボタンをクリックすると、テキストだけのモーダルが開きます。</p>
<div class="mt-6"><button @click="openModal(item,1)" class="btn">モーダルを開く</button></div>
</div>
</div>
<Modal :modalData="modalItem" v-show="showFlag"/> <!-- 追加 -->
</div>
</template>
<script>
import Modal from '@/components/about/Modal.vue' <!-- 追加 -->
export default {
name: 'About',
components: { <!-- 追加 -->
Modal
},
data(){
return {
showFlag: false,
modalItem: '',
item: [
{
title: 'テキストだけのモーダル01',
text: 'サンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキストサンプルテキスト',
img: 'slide01.jpg'
},
{
title: 'テキストだけのモーダル02',
text: 'サンプルテキストサンプルテキスト',
img: ''
}
]
}
},
methods: {
openModal (item,modalNumber){
this.showFlag = true;
this.modalItem = item[modalNumber];
},
closeModal(){
this.showFlag = false;
},
}
}
</script>
これで子にデータを送る準備ができました。
子コンポーネントでデータを受け取り、展開する
データを送ったら受け皿が必要になります。子要素にpropsで親から送られたデータを格納します。
<template>
<div class="Modal">
</div>
</template>
<script>
export default {
name: "Modal",
props: {
modalData: {
type: String,
default: "",
require: true
}
}
}
</script>
propsには親でバインドした名前を記述することで、親から送られてきたデータを受け取ることができます。
ただ受け取る場合はprops:['modalData']
という記述でも大丈夫ですが、この場合はどの型でも受け入れてしまいます。エラー検知やコンポーネントの使用方法を明確にするためにm」Vue公式では少なくとも1つの型を指定して受け取ったほうがよいと推奨されております。
受け取れたので、モーダルの中身を作っていきましょう。
<template>
<div class="Modal">
<div class="fixed bg-black bg-opacity-60 w-full h-full inset-0" >
<div class="absolute w-full h-full inset-0 p-6">
<div class="w-full mx-auto max-w-screen-sm bg-white">
<div class="w-full mx-auto max-w-screen-sm bg-white absolute transform -translate-y-1/2 -translate-x-1/2 top-1/2 left-1/2 max-h-screen rounded-md p-4">
<p class="text-2xl pt-4 pb-2 border-b-2 border-gray-700 border-solid">{{modalData.title}}</p>
<div class="max-h-80p min-h-200">
<p class="text-base">{{modalData.text}}</p>
<div class="mt-3" v-if="modalImg">
<img :src='require("@/assets/img/common/" + modalData.img)' alt="">
</div>
</div>
<div class="mt-6 flex">
<button class="Btn">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: {
modalData: {
type: String,
default: "",
require: true
}
},
data() {
return {
modalImg: false
}
},
watch: {
modalData: function(){
return this.modalImg = this.modalData.img.length ? true : false
}
}
}
</script>
受け取ったデータは、modalData.title
のように双方向バイティングで記述することができます。イメージデータを受け取って表示させる場合はsrcをバインドするのですが、そのままだと文字列が表示されてしまいます。今回のように@を使ってパスをたどる場合は、requireでモジュールとして読むこむことで表示ができるようになります。
受け取ったデータの中にイメージがない場合も想定されるので、watchでmodalDataを監視し、データの中にイメージファイルの記述があったらmodalImg
の真偽値を変更しv-if
で表示・非表示を切り替えています。
ちなみに、v-ifはundefinedだとエラーが起きるため、データを監視し初期値を設定する必要があります。
子から親にモーダルを閉じる司令をだす
モーダルはモーダル内のcloseボタンと背景のオーバーレイをクリックすることで、モーダルが閉じる仕様です。そのためには親コンポーネントにあるshowFlag
をfalse
にする必要があります。親のデータを変更するために、親コンポーネントにshowFlag
をfalse
する関数を設定し、子から親にはイベントを$emit
で送ってあげます。
<template>
<div class="Modal">
<div class="fixed bg-black bg-opacity-60 w-full h-full inset-0" >
<div class="absolute w-full h-full inset-0 p-6" @click.self="$emit('close')"><!-- 追加 -->
<div class="w-full mx-auto max-w-screen-sm bg-white">
<div class="w-full mx-auto max-w-screen-sm bg-white absolute transform -translate-y-1/2 -translate-x-1/2 top-1/2 left-1/2 max-h-screen rounded-md p-4">
<p class="text-2xl pt-4 pb-2 border-b-2 border-gray-700 border-solid">{{modalData.title}}</p>
<div class="max-h-80p min-h-200">
<p class="text-base">{{modalData.text}}</p>
<div class="mt-3" v-if="modalImg">
<img :src='require("@/assets/img/common/" + modalData.img)' alt="">
</div>
</div>
<div class="mt-6 flex">
<button class="Btn" @click.self="$emit('close')">Close</button><!-- 追加 -->
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<!-- 以下省略 -->
子コンポーネントにclickイベントを設定し、そこに$emit('close')
を記述しました。これは、親にclose
という名のイベントを送りつけてます。
また、.self
はターゲットが自分自身の場合のみハンドラを呼び出される設定であり、これによってバブリングを阻止しております。
<template>
<div class="About">
<h1 class="text-3xl py-8 px-4 bg-gray-400">About</h1>
<div class="max-w-screen-xl mx-auto mt-10 px-4">
<div>
<p class="text-base">このボタンをクリックすると、画像つきのモーダルが開きます。</p>
<div class="mt-6"><button @click="openModal(item,0)" class="btn">モーダルを開く</button></div>
</div>
<div class="mt-8">
<p class="text-base">このボタンをクリックすると、テキストだけのモーダルが開きます。</p>
<div class="mt-6"><button @click="openModal(item,1)" class="btn">モーダルを開く</button></div>
</div>
</div>
<Modal :modalData="modalItem" v-show="showFlag" @close="closeModal()"/> !-- 追加 -->
<Footer></Footer>
</div>
</template>
<script>
<!-- 一部省略 -->
methods: {
openModal (item,modalNumber){
this.showFlag = true;
this.modalItem = item[modalNumber];
},
closeModal(){!-- 追加 -->
this.showFlag = false;
},
}
}
</script>
親コンポーネントにはインポートしている<Modal />
に@close="closeModal()"
を追記しました。これで子から送られてきたclose
というイベントをキャッチし、closeModal()
という関数を動かすことができます。closeModal()
には'showFlag'をfalse
にする処理が記述してあり、真偽値が変更されることで、v-show
の表示が変更になり、モーダルが閉じられます。
これで1つのコンポーネントで取り回しができ、モーダルの数量が増えてもモーダルデータのみを増やせばOKなモーダルが作成できました。