iPhoneでもBootstrap4のモーダルを使いたい

今回記事を担当しているのは、新米プログラマーその２👩です！
Nuxt.jsやBootstrap4を使い始めて約２ヶ月😇
初心者ながら、ぶつかった壁を乗り越えたので書き残しておきます。

# 何が起こったか
**Bootstrap4のモーダルがiPhoneでうまく動かない。**
何かと言うと、

- モーダルの背景がスイッとスクロールされてしまう！
- 肝心のモーダルは止まっちゃったりガタガタとスクロールされたりする！

モーダルの縦の長さが画面の縦サイズより大きくなったとき、とても困ります😡
ちなみに、「iOSでモーダルの背景が動いちゃう〜><」という現象はよくあるみたいです。なんだと。

[iOSでモーダルが上手くスクロールできない問題への対応](https://slash-mochi.net/?p=1189)
[iOS Safari で絶対位置指定された要素のスクロールが出来ない件](https://qiita.com/chocolamint/items/67d25933880404c93db0)
[iphone – ModalでスクロールするとSafariで背景がスクロールする](https://codeday.me/jp/qa/20190430/737007.html)

...などなど

# 原因わからず
「Safariがダメ」「GoogleChromeがダメ」「iPhoneだけがダメ」…etc.
色々と原因は挙げられていましたが、サイトによっては他のサイトと真逆のことを言っていることもありました。
はっきりした原因はわからないまま、ひとまず日本語で書かれた解決法は試しました。全滅。
英語で書かれているものも読みました。全滅。
この時点でBootstrap4のモーダルは諦めました。

# BootstrapVueへ
引っ越しました。
モーダルやらプルダウンやら使ってみましたが、**「Vue.js向けのBootstrap4！便利！」**という印象です。
Bootstrap4はイベントをjQueryで指定しなきゃいけないことがあるんですね。そう、モーダルとかね。
対してBootstrapVueでは@イベントが用意されています！
というわけで、がんばって英語を読んで実装しました。

[BootstrapVue公式ドキュメント](https://bootstrap-vue.js.org/)

## インストール
こちらを参考に。

[Bootstrap を Vue.js で使ってみよう](https://qiita.com/kurararara/items/620dd4de798a941099f5)

# イベントでカスタマイズ

ページとコンポーネントに分けて実装しました。
この時点でカスタマイズしたのは、フッターのボタンと、それに対するclickイベントです。
フッターについては、`<footer slot="modal-footer"></footer>`でサクッと変更できます😊

```components/modal.vue
<template>
  <div>
    <!-- モーダルを表示するボタン クリックされたらモーダルを表示する関数を呼ぶ -->
    <b-button @click="showModal">
      open!
    </b-button>
    <!-- モーダル部分 タイトルはここで指定する -->
    <b-modal ref="cardNameModal" title="CARD NAME">
      <!-- ボディ部分に表示 -->
      <div class="d-block text-center">
        <h3>白魔道士ピケル</h3>
      </div>
      <!-- フッター部分をカスタマイズ -->
      <footer slot="modal-footer">
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="secondary" @click="hideModal">
          閉じる
        </b-button>
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="primary" @click="hideModal">
          完了
        </b-button>
      </footer>
    </b-modal>
  </div>
</template>

<script>
export default {
  methods: {
    // モーダルを表示する関数
    showModal() {
      this.$refs.cardNameModal.show()
    },
    // モーダルを閉じる関数
    hideModal() {
      this.$refs.cardNameModal.hide()
    }
  }
}
</script>
```

```pages/index.vue
<template>
  <div>
    <modalComponent></modalComponent>
  </div>
</template>

<script>
import modalComponent from '~/components/modal.vue'

export default {
  components: {
    modalComponent
  }
}
</script>
```

これをベースにイベントを追加し、スクロールを固定していきます。
必要なのは次の２点。

1. モーダルを表示するとき、背景のスクロールを固定する -> showイベント
2. モーダルを閉じるとき、背景のスクロール固定を解除する -> hiddenイベント

これでカスタマイズして完ぺ…**ちょっと待ったー！！**
ここで運命の分かれ道！ファイルの構成を確認します！

1. ページとコンポーネントに分けず、1つのファイルに書いている
2. ページとコンポーネントに分けている

1の場合は、そのままページに対してスクロールを固定すればOKです。
クラスのバインディングがスマートな書き方だと思います😉

[Vue.js公式 クラスとスタイルのバインディング](https://jp.vuejs.org/v2/guide/class-and-style.html#ad)

今回は2のパターンな訳ですが…
他にもモーダルを使う部分が出てくるとなると、その度にスクロール固定のクラスを書かなくてはいけません。
そして、スクロールを固定しなくてはならないファイルに一つ一つ指定するのも大変…。

というわけで、今回は**scss**と**jQuery**を使ってスクロールを固定・解除する方法を取りました！
(Nuxt.jsを使っている以上、できる限りjQueryは避けたいところですが妥協しました。)

```components/modal.vue
<template>
  <div>
    <!-- モーダルを表示するボタン クリックされたらモーダルを表示する関数を呼ぶ -->
    <b-button @click="showModal">
      open!
    </b-button>
    <!-- モーダル部分 タイトルはここで指定する -->
    <!-- show,close,hiddenイベントを追加 -->
    <b-modal
      ref="cardNameModal"
      title="CARD NAME"
      @show="showModal"
      @close="hideModal"
      @hidden="hideModal">
      <!-- ボディ部分に表示 -->
      <div class="d-block text-center">
        <h3>白魔道士ピケル</h3>
      </div>
      <!-- フッター部分をカスタマイズ -->
      <footer slot="modal-footer">
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="secondary" @click="hideModal">
          閉じる
        </b-button>
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="primary" @click="hideModal">
          完了
        </b-button>
      </footer>
    </b-modal>
  </div>
</template>

<script>
export default {
  methods: {
    // モーダルを表示する関数
    showModal() {
      this.$refs.cardNameModal.show()
      // スクロールを固定するクラスを追加する
      $('body').addClass('showing_modal')
    },
    // モーダルを閉じる関数
    hideModal() {
      this.$refs.cardNameModal.hide()
      // スクロールを固定するクラスを解除する
      $('body').removeClass('showing_modal')
    }
  }
}
</script>
```

```pages/index.vue
<template>
  <div>
    <modalComponent></modalComponent>
  </div>
</template>

<script>
import modalComponent from '~/components/modal.vue'

export default {
  components: {
    modalComponent
  }
}
</script>
```

```assets/scss/custom.scss
// モーダルを表示したとき、背景のスクロールを固定する
.showing_modal {
  position: fixed;
  overflow: hidden;
}

@import 'bootstrap/scss/bootstrap.scss';
@import 'bootstrap-vue/src/index.scss';
```

scss部分に関してはこちらを参考にしています😉

[iOSでモーダルが上手くスクロールできない問題への対応](https://slash-mochi.net/?p=1189)

これでiPhoneでもモーダルのスクロールに悩まされなくなりました！
あとはバシバシお好みの見た目にカスタマイズしましょ〜

## hiddenイベントについて
個人的に面白いな、と思ったのが**hiddenイベント**。
どこで役立ったかというと、「モーダルの背景をクリックする」という場合。

BootstrapVue「モーダルの背景をクリックされたら閉じてあげる。スクロール固定のクラス？知らないよ😆」

…つまり、「モーダルを閉じたイベント」に対してスクロール固定を解除する必要が出てくるわけです。
そのイベントこそがhiddenイベント！
他のイベントと同じようにクラス解除の指定をしてあげるだけで無事解決です。

# まとめ
Vue.jsやNuxt.jsとともにBootstrap4を使って慣れてきたら、ぜひBootstrapVueも使ってみてください。
初心者でもある程度使われた方なら、きっと壁を超える方法が見つかります😊