0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Nuxt.js】1ページで複数のモーダルウィンドウを実装する

0
Posted at

■目的

勉強のためNuxt.jsのサンプル集を作る

目標3

モーダルウィンドウを実装する

ざっくり仕様

  • 配列からコンテンツを読み込んでタップすると該当の内容がモーダルウィンドウで表示される
  • PCではモーダルウィンドウの機能を解除する(ただ並べる)
  • モーダルウィンドウ展開時、背景の黒いところをタップで閉じる
  • モーダルウィンドウ展開時、後ろでスクロールさせない
  • モーダルウィンドウのロジックはstoreで共通管理したい

default.vue

ベースファイルはここで書いたものと同じ。
components/template/SampleList.vue内で処理をしていく。

SampleList.js

まずは読み込むための配列データを用意する。

assets/js/SampleList.js
const sampleList = [
  {
    id: '1',
    path: 'hamburger-menu',
    img: '/images/sample/hamburger-menu.jpg',
    name: 'Hamburger Menu',
    nameJa: 'ハンバーガーメニュー',
    comment: '言わずと知れた「三本線」のボタンクリックでメニューの開閉を実行。ヘッダ右上のボタンがそれ。(PC版では非表示)'
  },
  {
    id: '2',
    path: 'modal-window',
    img: '/images/sample/modal-window.jpg',
    name: 'Modal Window',
    nameJa: 'モーダルウィンドウ',
    comment: 'ウィンドウ内で、「子ウィンドウ」を展開。1ページ複数のモーダルウィンドウに対応'
  },
  {
    id: '3',
    path: 'accordion',
    img: '/images/sample/accordion.jpg',
    name: 'Accordion',
    nameJa: 'アコーディオン',
    comment: 'ボタンクリックで下に展開するアコーディオンメニュー(トグルメニュー)。ベタ書きと配列処理の2パタン'
  },
  {
    id: '4',
    path: 'tabs-menu',
    img: '/images/sample/tabs-menu.jpg',
    name: 'Tabs Menu',
    nameJa: 'タブメニュー',
    comment: 'タブ切り替え機能。PC・スマホともにタブメニューと、PCではタブ、スマホではアコーディオンの2パタン。どちらも配列処理'
  }
]

export default sampleList

たとえば「タブメニュー」をタップするとその内容がモーダルウィンドウで開くように実装する。

Modal.js

次に、storeディレクトリに置くモーダルウィンドウのスクリプト。
シンプルにフラグを切り替えるのみにした。
(正直、Storeについてまだ全然理解していないので、むしろこれしか書けなかった)

store/Modal.js
export const state = () => ({
  modalFlag: false
})
export const mutations = {
  openModal (state) {
    state.modalFlag = true
  },
  closeModal (state) {
    state.modalFlag = false
  }
}

statemodalFlagのデフォルト値を管理して、
mutationsの中で関数を書いてmodalFlagの真偽値を変更する。

SampleList.vue

さて書いていく。

components/template/SampleList.vue
<template>
  <ul class="c-list_sample">
    <li v-for="sample in limitCount" :key="sample.id" class="c-modal" :class="modalItem.path">
      <template v-if="$window.width < 1024">
        <AtomsButtonsTextBtn
          btn-style="modal"
          :link-text="sample.name"
          color="white"
          @open-modal="openModal(sample)"
        />
        <MoleculesEtcModal
          v-if="isModalState"
          :top-position="$window.pageYOffset"
          @close-modal="$store.commit('Modal/closeModal')"
        >
          <dl>
            <dt>
              <span>{{ modalItem.name }}</span><br>
              {{ modalItem.nameJa }}
            </dt>
            <dd>
              <p class="comment f-txt">
                {{ modalItem.comment }}
              </p>
              <AtomsButtonsTextBtn
                btn-style="ghost"
                color="white"
                :link-path="modalItem.path"
                :link-text="modalItem.name"
              />
            </dd>
          </dl>
        </MoleculesEtcModal>
      </template>
      <template v-else>
        <figure><img :src="sample.img" width="960" height="960"></figure>
        <dl>
          <dt>
            <span>{{ sample.name }}</span><br>
            {{ sample.nameJa }}
          </dt>
          <dd>
            <p class="comment f-txt">
              {{ sample.comment }}
            </p>
            <AtomsButtonsTextBtn
              btn-style="ghost"
              color="white"
              :link-path="sample.path"
              :link-text="sample.name"
            />
          </dd>
        </dl>
      </template>
    </li>
  </ul>
</template>

<script>
import sampleList from 'assets/js/SampleList'

export default {
  data () {
    return {
      modalItem: ''
    }
  },
  head () {
    return {
      bodyAttrs: {
        class: this.isModalState ? 'modal-on' : ''
      }
    }
  },
  computed: {
    limitCount () {
      if (this.$route.name === 'index') {
        return sampleList.slice(0, 3)
      } else {
        return sampleList
      }
    },
    isModalState () {
      return this.$store.state.Modal.modalFlag
    }
  },
  methods: {
    openModal (sample) {
      this.$store.commit('Modal/openModal')
      this.modalItem = sample
    }
  }
}
</script>

Windowサイズを取得して条件分岐
windowサイズの取得は前回同様、こちらの記事のコードを拝借した。

モーダルウィンドウを開くメソッド
@open-modal="openModal(sample)"は子コンポネントのボタンから@click$emitで受け取り、sampleを引数に

MoleculesEtcModal

  • v-if="isModalState"isModalStatetrueのときに
  • :top-position="$window.pageYOffset"で先にWindowサイズ取得コードでスクロール量を子コンポネントに渡す
  • @close-modal="$store.commit('Modal/closeModal')"でstoreに設定した`スクリプトを読み込む
  • MoleculesEtcModalの中はコンポネント側で<slot />を指定しているので自由に記述

<template v-else>はただ読み込んでいるだけなので割愛して……

SampleList.jsの読み込み
import sampleList from 'assets/js/SampleList'で読み込む

modalItem
dataの中でreturnしているmodalItemは、モーダルウィンドウを開いたときに、該当する配列のデータを格納するための変数。これがないと一番最後の配列の値が一番上に来てしまう(自分はこの方法しか解決方法を見つけられなかった)

head()のbodyAttr
class: this.isModalState ? 'modal-on' : ''これを記述することで、isModalStatetrueのとき、<body>modal-onというclassを付与する。

limitCount()
これはなにかというと、いわゆるトップページのときは配列を3個だけ取得して、そうでないときは全部取得しなさいよということで、別になくてもいい。

isModalState()
return this.$store.state.Modal.modalFlagでstoreで設定した真偽値を返している。

openModal()

  • this.$store.commit('Modal/openModal')でstoreで設定したスクリプトを読み出し
  • this.modalItem = samplesamplemodalItemに格納する。

Modal.vue

SampleList.vueで読み込んでいる子コンポネント

components/molecules/etc/Modal.vue
<template>
  <transition name="modal" appear>
    <div
      class="c-modal_base"
      :style="modalTopPosition"
      @click.self="closeModal"
    >
      <div class="c-modal_wrap">
        <div class="c-modal_content">
          <slot />
        </div>
        <AtomsIconsTimeIcn
          @click.native="closeModal"
        />
      </div>
    </div>
  </transition>
</template>

<script>
export default {
  props: {
    topPosition: {
      type: Number,
      default: 0
    }
  },
  computed: {
    modalTopPosition () {
      return {
        '--top': this.topPosition + 'px'
      }
    }
  },
  methods: {
    closeModal () {
      this.$emit('close-modal')
    }
  }
}
</script>

<style lang="scss">
.c-modal{
  &_base{
    position: absolute;
    left: 0;
    top: var(--top);
    z-index: 1000;
    width: 100%;
    height: 100%;
    background-color: rgba($dark, 0.7);
  }

  &_wrap{
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: 1100;
    width: 90%;
    height: 80%;
    transform: translate(-50%, -50%);

    .icon.time{
      right: 0;
      top: -1.5em;
      width: 2em;
      height: 2em;
    }
  }

  &_content{
    overflow-y: scroll;
    width: 100%;
    height: 100%;
    padding: 1em;
    background-color: rgba($white, 0.9);
    border: 2px $black solid;
  }
}

//transition
.modal{
  &-enter-active,
  &-leave-active{
    transition: opacity 0.6s ease;

    .c-modal_content{
      transition: opacity 0.4s ease, transform 0.8s ease;
    }
  }
  &-leave-active{
    transition: opacity 0.6s ease 0.1s;
  }

  &-enter,
  &-leave-to{
    opacity: 0;

    .c-modal_content{
      opacity: 0;
      transform: translateY(-20px);
    }
  }
}
</style>
  • @click.self="closeModal".selfを指定することで、子要素をタップしても発火しないように設定する
  • propstopPositionを受け取り、computedmodalTopPosition()でcssの値をreturnする。それをc-modal_basetopに指定することで、どこでモーダルウィンドウを開いても画面の上部から始まるようにする

body.modal-on

cssで以下を設定する。

hidden;
width: 100%;
height: 100%;```

## 結果
求めていた通りのモーダルウィンドウができた。
でもコードをもっとスリムにしたい。まだまだ研究の余地がありそうだ。
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?