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

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


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

#### ＊関連ワード
Vue.js 3系 / TypeScript / SCSS / component / Mac OS / Modal / emit


## 完成形

![modal.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365722/5461778c-73e6-caab-4420-5511244679c2.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ページとして表示させてある状態です。


```vue: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 省略 
```

```vue: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にて

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

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



#### ＊templateにて ＜モーダルウィンドの構成②＞
`  <div id="overlay" v-on:click="closeModal()">`**の
divタグで表示させたいところを囲む。**
※ v-on:click="closeModal( )"については、後ほど説明

```vuejs: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365722/1bdd2ff3-b02f-7b9f-2af0-00b71a4a54d4.png)


#### ＊scriptにて　 ＜モーダルウィンドの構成③＞


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


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



```vuejs: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>`
↑ここの`親メソッド名`の処理が呼ばれることになる。

<details><summary>**＊Emit 子→親にデータを渡す処理**</summary>
今回はデータを渡すというよりは、“クリックされたよ〜！”
って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>`であると考えてもらって大丈夫です！

```vuejs: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365722/23e01e60-4955-d061-07fe-438ddf197387.png)


####＊scriptにて ＜モーダルウィンド完成＞
`setup()`内にてモーダルの表示の有無を宣言する
` let showContent = ref(false);` 
`trueならモーダル表示` / `falseならモーダル非表示`

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

`  openModal()` / `closeModal()`

```vuejs: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>
```

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


#### ＊templateにて　 


```vuejs: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タグも含めて掲載。
汚いコードであるのも承知してます・・・

```vuejs: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>

```

```vuejs: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系でのモーダルの記事があまりなかったので
忘備録して掲載してみました。

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