■目的
勉強のためNuxt.jsのサンプル集を作る
目標3
モーダルウィンドウを実装する
ざっくり仕様
- 配列からコンテンツを読み込んでタップすると該当の内容がモーダルウィンドウで表示される
- PCではモーダルウィンドウの機能を解除する(ただ並べる)
- モーダルウィンドウ展開時、背景の黒いところをタップで閉じる
- モーダルウィンドウ展開時、後ろでスクロールさせない
- モーダルウィンドウのロジックはstoreで共通管理したい
default.vue
ベースファイルはここで書いたものと同じ。
components/template/SampleList.vue内で処理をしていく。
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についてまだ全然理解していないので、むしろこれしか書けなかった)
export const state = () => ({
modalFlag: false
})
export const mutations = {
openModal (state) {
state.modalFlag = true
},
closeModal (state) {
state.modalFlag = false
}
}
stateでmodalFlagのデフォルト値を管理して、
mutationsの中で関数を書いてmodalFlagの真偽値を変更する。
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"でisModalStateがtrueのときに -
: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' : ''これを記述することで、isModalStateがtrueのとき、<body>にmodal-onというclassを付与する。
limitCount()
これはなにかというと、いわゆるトップページのときは配列を3個だけ取得して、そうでないときは全部取得しなさいよということで、別になくてもいい。
isModalState()
return this.$store.state.Modal.modalFlagでstoreで設定した真偽値を返している。
openModal()
-
this.$store.commit('Modal/openModal')でstoreで設定したスクリプトを読み出し -
this.modalItem = sampleでsampleをmodalItemに格納する。
Modal.vue
SampleList.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を指定することで、子要素をタップしても発火しないように設定する -
propsでtopPositionを受け取り、computedのmodalTopPosition()でcssの値をreturnする。それをc-modal_baseのtopに指定することで、どこでモーダルウィンドウを開いても画面の上部から始まるようにする
body.modal-on
cssで以下を設定する。
width: 100%;
height: 100%;```
## 結果
求めていた通りのモーダルウィンドウができた。
でもコードをもっとスリムにしたい。まだまだ研究の余地がありそうだ。