LoginSignup
2
0

More than 1 year has passed since last update.

*Vue 3系* モーダルウィンドを作成

Posted at

はじめに

Vue3系を勉強し始めてcomponentを使う練習をしたかったので
モーダルを作ってみた時の忘備録です。
また、初心者のため
Emit(子→親)についても整理しながらの記事になります。
もうVueを使い倒している方には向かない内容につき
時間の無駄になってしまうので読まないでくださいね!!!

私は、今までVue2系の"vue-property-decorator"を使用していたので
Vue3系の勉強をしながら作成したものになります。
不備等もあると思いますがご了承ください。

*関連ワード

Vue.js 3系 / TypeScript / SCSS / component / Mac OS / Modal / emit

完成形

modal.gif

ディレクトリ構成

ディレクトリ内
※ storeやrouterなど使用していないものは省略しています
src/
 ┣ components/
 │  ├ Header.vue
 │  ├ Modal.vue
 │ 
 ┣ views/
 │  ├ Home.vue
 │ 
 ┣ App.vue

モーダルウィンドの構成

① Header.vue に作成したハンバーガーボタンをクリックする(親コンポーネント)
       ↓
② Modal.vue がモーダルで表示される(子コンポーネント)
       ↓
③ Modal.vue の背景をクリックするとモーダルが閉じる(子→親)

というシンプルな構成です。

*前提として
App.vue でHeaderコンポーネントを表示
海の背景は Home.vue としてviewsに格納してあり、
Topページとして表示させてある状態です。

App.vue
<template>
  <Header></Header>
  <router-view />
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Header from "./components/Header.vue";
export default defineComponent({
  components: { Header },
});
</script>
// style 省略 
Home.vue
<template>
  <div class="container">
    <div class="top-img">
      <img src="/img/sea.jpg" alt="人魚のシルエット" />
    </div>
  </div>
</template>

// script 省略

<style lang="scss">
.container {
  width: 100%;
}
.top-img {
  position: relative;
  width: 100%;
  height: 100vh;
  img {
    width: 100%;
    height: 100vh;
  }
}
</style>

<子コンポーネント> Modal.vueにて

*モーダルウィンドの構成(再)
① Header.vue に作成したハンバーガーボタンをクリックする(親コンポーネント)
       ↓
② Modal.vue がモーダルで表示される(子コンポーネント)
       ↓
③ Modal.vue の背景をクリックするとモーダルが閉じる(子→親)

まず最初に、
モーダルウィンドの構成②:モーダルウィンドが表示された時の画面(子コンポーネント)
モーダルウィンドの構成③:モーダルウィンドが背景をクリックするとモーダルが閉じる(子→親)

について触れていきます。

*templateにて <モーダルウィンドの構成②>

<div id="overlay" v-on:click="closeModal()">
divタグで表示させたいところを囲む。

※ v-on:click="closeModal( )"については、後ほど説明

Modal.vue
<template>
  <div id="overlay" v-on:click="closeModal()">
    <div id="content">
      <img src="img/ariel.png" alt="人魚のシルエット" />
      <span>モーダルウィンドが<br />表示されました</span>
    </div>
  </div>
</template>

cssは最後にまとめて載せますが
この画面が完成しました。
スクショ.png

*scriptにて  <モーダルウィンドの構成③>

Vue3系より使用できるようになったsetup()関数でコンポーネントを、
setup()第1引数にprops第2引数にcontextを持たせる。
これで 親子間のデータの引き渡しが可能となる。

モーダルが表示された後、
モーダルの背景をクリックするとモーダルウィンドが閉じるようにしたいので
このウィンド自体である<div id="overlay"> </div>
v-on:clickで背景がクリックされた時にモーダルが閉じるメソッドを持たせる

v-on:click="closeModal()

Modal.vue
<script lang="ts">
import { defineComponent } from "@vue/runtime-core";

export default defineComponent({
  setup(props, context) {
    /**
     * モーダルウィンドを閉じる.
     */
    let closeModal = () => {
      context.emit("close");
    };

    return {
      closeModal,
    };
  },
});
</script>

closeModal()メソッドの処理内容に

context.emit("close");
と書く。

この処理を記載することで
子コンポーネント内でcloseModal()が呼ばれた時に
親コンポーネントのtemlateタグに記載している、
<Header v-on:close="親のメソッド名"> </Header>
↑ここの親メソッド名の処理が呼ばれることになる。

*Emit 子→親にデータを渡す処理
今回はデータを渡すというよりは、“クリックされたよ〜!”
ってemitを通じて親に伝えているみたいな?ニュアンス??
context.emit("close")
↑この1文で親のcloseを呼び出すようなイメージ。
子コンポーネントで"close"で呼ばれたらこの処理をするというメソッドを
親コンポーネントに書いておくことで
親子関係が成り立っている。

次に親での処理をみていきます

<親コンポーネント> Header.vueにて

*templateにて  <モーダルウィンドの構成①>

今回クリックするボタンは、fontAwesomeのbarsを使用。
<i class="fas fa-bars"></i>

もし、分かりにくと感じた方は<div class="btn"> </div>自体が
<button type="button">ボタン</button>であると考えてもらって大丈夫です!

Header.vue
<template>
  <header>
    <div class="btn" v-on:click="openModal()">
      <i class="fas fa-bars"></i>
    </div>
    <transition name="fade">
      <Modal v-if="showContent" v-on:close="closeModal()"></Modal>
    </transition>
  </header>
</template>

v-on:clickやv-ifのところは次に詳細記載します。

この画面の完成
スクショ2.png

*scriptにて <モーダルウィンド完成>

setup()内にてモーダルの表示の有無を宣言する
let showContent = ref(false);
trueならモーダル表示 / falseならモーダル非表示

そしてこの表示の切り替えをするメソッドをそれぞれ作成する。

openModal() / closeModal()

Header.vue
<script lang="ts">
import { defineComponent, ref } from "@vue/runtime-core";
import Modal from "./HeaderModal.vue";

export default defineComponent({
  components: {
    Modal,
  },
  setup() {
    //モーダルクリックチェック
    let showContent = ref(false);

    /**
     * モーダルウィンドウを表示する.
     */
    let openModal = () => {
      showContent.value = true;
    };

    /**
     * モーダルウィンドを閉じる.
     */
    let closeModal = () => {
      showContent.value = false;
    };

    return { showContent, openModal, closeModal };
  },
});
</script>

*Vue3系でのコンポーネントの書き方
・親コンポーネントのscriptタグにて子コンポーネントをインポートする
import 子コンポ名A from "子コンポまでのパス"
defineComponent({})内でsetup()の前に使用するコンポーネントを書く
export default defineComponent({
components: { 子コンポ名A },
この子コンポ名Aでtemplateタグ内で表示させることができる

*templateにて 

Header.vue
<template>
  <header>
    <div class="btn" v-on:click="openModal()">
      <i class="fas fa-bars"></i>
    </div>
    <transition name="fade">
      <Modal v-if="showContent" v-on:close="closeModal()"></Modal>
    </transition>
  </header>
</template>

<div class="btn">v-on:click="openModal()"
scriptタグに用意したopenModal()メソッドを呼び
ボタンをクリックされたらモーダルが開く処理。

子コンポーネントであるModalには、v-ifを使って
trueなら表示、falseなら非表示とさせる。
<Modal v-if="showContent"></Modal>

最後に
v-on:close="closeModal()"で、
先ほど、子コンポーネントでemitで呼び出す処理にて
"close"が呼ばれたら、親コンポーネントのscriptダグに宣言した
closeModal()メソッドが呼ばれるようにしておく。

これで、全ての処理が終わり!

まとめ

最後にstyleタグも含めて掲載。
汚いコードであるのも承知してます・・・

Header.vue
<template>
  <header>
    <div class="btn" v-on:click="openModal()">
      <i class="fas fa-bars"></i>
    </div>
    <transition name="fade">
      <Modal v-if="showContent" v-on:close="closeModal()"></Modal>
    </transition>
  </header>
</template>

<script lang="ts">
import { defineComponent, ref } from "@vue/runtime-core";
import Modal from "./HeaderModal.vue";

export default defineComponent({
  components: {
    Modal,
  },
  setup() {
    //モーダルクリックチェック
    let showContent = ref(false);

    /**
     * モーダルウィンドウを表示する.
     */
    let openModal = () => {
      showContent.value = true;
    };

    /**
     * モーダルウィンドを閉じる.
     */
    let closeModal = () => {
      showContent.value = false;
    };

    return { showContent, openModal, closeModal };
  },
});
</script>

<style lang="scss">
header {
  font-size: 2rem;
  padding: 0.5rem 1rem;
  width: 100%;
  background-color: rgba(0, 0, 0, 0);
  position: fixed;
  box-sizing: border-box;
  text-align: end;
  z-index: 10;
}
// モーダルの出現スピード htmlの<transition > にて
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter-from, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
</style>

Modal.vue
<template>
  <div id="overlay" v-on:click="closeModal()">
    <div id="content">
      <img src="img/ariel.png" alt="アリエルの画像" />
      <span>モーダルウィンドが<br />表示されました</span>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "@vue/runtime-core";

export default defineComponent({
  setup(props, context) {
    /**
     * モーダルウィンドを閉じる.
     */
    let closeModal = () => {
      context.emit("close");
    };

    return {
      closeModal,
    };
  },
});
</script>

<style lang="scss" scoped>
.sp-header-container {
  width: 100%;
  height: 100vh;
}

#overlay {
  z-index: 1;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  background-color: rgba(156, 174, 183, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  transition-duration: 0.6s;
}
#content {
  z-index: 2;
  width: 55%;
  padding: 2em;
  margin: 0 auto;
  background-color: rgba(0, 0, 0, 0);
  text-align: center;

  span {
    display: block;
    width: 100%;
    font-weight: 900;
    font-size: 3rem;
    color: black;
  }

  img {
    width: 20rem;
    height: auto;
  }
}
</style>

最後に

こんなに最後まで読んでいただきありがとうございます。

Vue3系でのモーダルの記事があまりなかったので
忘備録して掲載してみました。

プログラミングの勉強始めたばかりで、
今回初めて掲載したため、説明の仕方、コード、やり方など
未熟なところばかりですが、これから勉強していきます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0