Vue.jsでモーダルウィンドウを実装する方法 【コピペOK】

  • 2021.10.12
  • 2022.05.04
  • Vue.js
Vue.jsでモーダルウィンドウを実装する方法 【コピペOK】

vue.jsで共通使用できるモーダルウィンドウの実装方法を知りたい!

こう言った疑問に答えていきます。

よく見かけるモーダルウィンドウの実装は、使用するコンポーネントに、モーダルウィンドウコンポーネントをインポートして使用することが多いかと思います。

ただそれだと、毎回インポートする上に、その都度メソッドを追加する必要がありとても面倒です。

そこで今回は、上記のような問題を解決するために、どのコンポーネントからでも呼び出し可能な共通使用できるモーダルウィンドウの実装方法を解説していきます。

では早速やっていきましょう。

Vue.jsでモーダルウィンドウを実装する方法

実装イメージ

まず始めに、実装する前に今回作成するモーダルウィンドウの考え方について解説していきます。

今回はどのコンポーネントからでも表示・処理を行うために「Vuex」を使用していきます。

イメージは下記のような感じです。

  1. モーダルウィンドウコンポーネントをApp.vue内でインポートし、どのコンポーネントからでも使用できるようにする。
  2. Vuexでモーダルウィンドウの表示・非表示の制御を行う。
  3. モーダルウィンドウ内の「OK」「キャンセル」ボタンをクリックした後に何かしらの処理が行えるように、Promiseを使って実装する。


では実際に実装していきましょう!

環境準備

下記手順で環境を構築していきます。
今回使用するファイルはGitHubにアップしているので、自由に活用してください。

# プロジェクト名は任意です。
$ vue create vue-modal-window
# 「Manually select features 」を選択
? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
  Default (Vue 3) ([Vue 3] babel, eslint) 
❯ Manually select features 

# Vuexを追加
? Check the features needed for your project: 
 ◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
❯◉ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

# 2系を選択
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
❯ 2.x 
  3.x

# lintの設定(ここはお好みです)
? Pick a linter / formatter config: 
  ESLint with error prevention only 
  ESLint + Airbnb config 
  ESLint + Standard config 
❯ ESLint + Prettier 

# ここはお好みです
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save
 ◯ Lint and fix on commit

# package.jsonを選択
? Where do you prefer placing config for Babel, ESLint, etc.? 
  In dedicated config files 
❯ In package.json 

# N を入力
? Save this as a preset for future projects? (y/N) N

これでインストールができました。
では下記コマンドで実際に起動してみましょう。

# 作成ディレクトリへ移動
$ cd vue-modal-window/

# サーバー起動
$ npm run serve

下記画面が表示されたらOKです!

モーダルを作成する

では次にモーダルを作成していきます。
src/components 配下に「Modal.vue」ファイルを作成します。
このコンポーネントがモーダルウィンドウになります。

デザインはお好みで適宜修正してください。

<template>
	<div class="modal">
		<div class="modal__box">
			<span class="modal__message">Message?</span>
			<div class="modal__action">
				<div class="modal__btn">キャンセル</div>
				<div class="modal__btn modal__btn--success">OK</div>
			</div>
		</div>
	</div>
</template>

<script>
export default {}
</script>

<style>
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

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

.modal__box {
	position: absolute;
	top: 50%;
	left: 50%;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	width: 500px;
	height: 200px;
	padding: 32px;
	background-color: #fff;
	border-radius: 4px;
	transform: translate(-50%, -50%);
}

.modal__message {
	font-size: 24px;
}

.modal__action {
	display: flex;
	justify-content: flex-end;
}

.modal__btn {
	width: 130px;
	margin-left: 16px;
	padding: 8px;
	line-height: 1.5;
	font-weight: bold;
	border: 1px solid rgb(196, 196, 196);
	cursor: pointer;
}

.modal__btn--success {
	color: #fff;
	border: none;
	background-color: rgb(60, 140, 245);
}
</style>

作成ができたら、ModalコンポーネントをApp.vueにインポートしていきます。

<template>
	<div id="app">
		<img alt="Vue logo" src="./assets/logo.png" />
		<HelloWorld msg="Welcome to Your Vue.js App" />
		<Modal />  // 追加
	</div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
import Modal from './components/Modal.vue' // 追加

export default {
	name: 'App',
	components: {
		HelloWorld,
		Modal,  // 追加
	},
}
</script>

<style>
#app {
	font-family: Avenir, Helvetica, Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-align: center;
	color: #2c3e50;
	margin-top: 60px;
}
</style>

追加できたら、実際に表示を確認して見ましょう!

無事表示できたら、次にロジックを実装していきます。

モーダルを表示する

冒頭で述べたように、Vuexを使って実装していくので、src/store/index.jsファイルに諸々書いていきます。

まず始めにロジックをモーダルの表示・非表示制御を実装していきます。
Vuexにモーダル表示用の「isOpen」とメッセージテキストを格納する「message」をオブジェクト形式で追加します。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		modal: {
			isOpen: false,
			message: '',
		},
	},
	mutations: {},
	actions: {},
	modules: {},
})

追加できたら、これらの値を変更する「mutations」「actions」を書いていきます。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		modal: {
			isOpen: false,
			message: '',
		},
	},
	mutations: {
    // 追加
		commitModalOpen(state, payload) {
			state.modal = payload
		},
	},
	actions: {
    // 追加
		actionModalOpen({ commit }, payload) {
			commit('commitModalOpen', payload)
		},
	},
	modules: {},
})

「actionModalOpen」でpayloadを受け取り、mutationsの「commitModalOpen」でpayloadをstateへ代入します。
このpayloadには 「isOpen」「message」の2つが入ってくる想定です。

実際に表示されるか見てみましょう。
App.vueに下記を追加していきます。

<template>
	<div id="app">
		<img alt="Vue logo" src="./assets/logo.png" />
		<HelloWorld msg="Welcome to Your Vue.js App" />
		<button @click="open()">モーダルを表示する</button> // 追加
		<Modal v-if="isOpen" /> // 追加
	</div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
import Modal from './components/Modal.vue'

export default {
	name: 'App',
	components: {
		HelloWorld,
		Modal,
	},
	computed: {
    // 追加
		isOpen() {
			return this.$store.state.modal.isOpen
		},
	},
	methods: {
    // 追加
		open() {
			const payload = {
				isOpen: true,
				message: '変更しますがよろしいですか?',
			}
			this.$store.dispatch('actionModalOpen', payload)
		},
	},
}
</script>

<style>
// 省略
</style>

storeに格納している「isOpen」をcomputedで定義し、Modalコンポーネントにv-ifを追加します。
これで「isOpen」がtrueの時はモーダルを表示、falseなら非表示という処理になります。

またpayloadのmessageに渡したテキストを表示したいので、Modalコンポーネントのメッセージの部分を動的に変更されるように修正します。

<template>
	<div class="modal">
		<div class="modal__box">
			<span class="modal__message">{{ message }}</span> // 修正
			<div class="modal__action">
				<div class="modal__btn">キャンセル</div>
				<div class="modal__btn modal__btn--success">OK</div>
			</div>
		</div>
	</div>
</template>

<script>
export default {
	computed: {
    // 追加
		message() {
			return this.$store.state.modal.message
		},
	},
}
</script>

<style>
// 省略
</style>

これでpayloadに渡したメッセージを表示させることができます。

追加できたら、「モーダルを表示する」ボタンをクリックしてモーダルが表示されることを確認します。

モーダルのボタン処理を実装する

最後にモーダル内のボタンをクリックした時の処理を追加していきます。
ここではJavaScriptのPromiseを使用して実装していきます。

イメージ的には下記のような感じです。

  1. モーダル表示時に、Promiseインスタンスを生成。
  2. 生成時に取得できる「resolve」「reject」をstateに保存
  3. モーダル内の「OK」ボタンがクリックされたら「resolve」を実行させる。「キャンセル」ボタンなら「reject」を実行させる
  4. モーダルの呼び出し元に、実行結果を返す


では実際に書いて見ましょう!

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		modal: {
			isOpen: false,
			message: '',
			resolve: (bool) => bool, // 追加
			reject: (bool) => bool, // 追加
		},
	},
	mutations: {
		commitModalOpen(state, payload) {
			state.modal = payload
		},
   // 追加
		commitResetModalState(state) {
			state.modal = {
				type: '',
				text: '',
				resolve: (bool) => bool,
				reject: (bool) => bool,
			}
		},
	},
	actions: {
		actionModalOpen({ commit }, payload) {
       // 追加
       // 1.Promiseインスタンスを生成
			return new Promise((resolve, reject) => {
				const option = {
					resolve,
					reject,
					...payload,
				}
     // 2.payloadのデータと「resolve」「reject」をstateに保存
				commit('commitModalOpen', option)
			})
		},
   // 追加
    // 3.モーダル内の「OK」ボタンがクリックされたら「resolve」を実行させる。
		actionModalResolve({ commit, state }) {
			state.modal.resolve(true) // 4.モーダルの呼び出し元に、実行結果を返す
			commit('commitResetModalState')
		},
   // 追加
    // 3.「キャンセル」ボタンなら「reject」を実行させる
		actionModalReject({ commit, state }) {
			state.modal.reject(false) // 4.モーダルの呼び出し元に、実行結果を返す
			commit('commitResetModalState')
		},
	},
	modules: {},
})

これでモーダル内の各ボタンをクリックした時に、処理を実行させることができます。

ではModal.vueとApp.vueも修正していきます。

Modal.vueには、「OK」ボタンをクリックした時に「actionModalResolve」を実行させるようにし、「キャンセル」ボタンをクリックした時は、「actionModalReject」を実行させるようにします。

App.vueには、モーダル内のボタンが押された時の処理を追加します。ボタンが押されると、呼び出し元(今回ならApp.vue)にPromiseの結果を返すので、.then()、.catch() で結果を受け取れるようにします。

<template>
	<div class="modal">
		<div class="modal__box">
			<span class="modal__message">{{ message }}</span>
			<div class="modal__action">
				<div class="modal__btn" @click="modalReject">キャンセル</div> // 追加
				<div class="modal__btn modal__btn--success" @click="modalResolve">OK</div> // 追加
			</div>
		</div>
	</div>
</template>

<script>
export default {
	computed: {
		message() {
			return this.$store.state.modal.message
		},
	},
	methods: {
    // 追加
		modalReject() {
			this.$store.dispatch('actionModalReject', false)
		},
    // 追加
		modalResolve() {
			this.$store.dispatch('actionModalResolve', true)
		},
	},
}
</script>

<style>
// 省略
</style>
// 省略
	methods: {
		open() {
			const payload = {
				isOpen: true,
				message: '変更しますがよろしいですか?',
			}
			this.$store
				.dispatch('actionModalOpen', payload)
				.then(() => { // 追加
					// 「OK」ボタンが押された時の処理をここに書く
				})
				.catch(() => { // 追加
					// 「キャンセル」ボタンが押された時の処理をここに書く
				})
		},
	},

これで実装は終了です!
では実際に動かして見ましょう!
わかりやすいようにそれぞれボタンが押されたらアラートを表示してみます。

■ モーダルを表示する

■ OKボタンをクリックする。

■ キャンセルボタンをクリックする。

まとめ

今回はVue.jsでモーダルウィンドウを実装する方法について解説してきました。

この方法を使うことで、都度使用するコンポーネントにモーダルコンポーネントをインポートして、ボタンのメソッドを追加して〜といった手間がなくなります。

他にも応用として、ボタンのテキストを用途に応じて変更したり、注記を箇条書きで表示したりなど色々あるので試して見てください!

では今回は以上になります。