Nuxt.js + Vuetifyでフルスクリーンのローディング画面を実装する
Nuxt.js + Vuetifyでフルスクリーンのローディング画面を実装する方法を説明します。
作るもの
この記事では、以下の条件でローディング画面を実装します。
- Nuxt.jsの レイアウト機能 を使う
- layout側(
default.vue
)にスピナーのコンポーネントを置いて、pages側(index.vue
)からそれの表示/非表示を切り替える - スピナーの見た目は、画面全体を半透明のレイヤーで覆って、画面の中央にスピナーを配置する
実際に動かすと以下のような感じになります👍
v-progress-circular
使いつつCSSを書く
1. スピナーの表示にはVuetifyの v-progress-circular
を使います。 indeterminate
オプションを付けることで無限に回転し続けるスピナーを出力できます。
今回作ろうとしているローディング画面はとても一般的なものなので、何なら v-progress-circular
に標準でそういう見た目の出力オプションが用意されているかな?と思ったのですが、どうやら無さそうなので自力でCSSを書くことにします。
ググったところ(ちょっと古いですが)この辺 が出てきて、自分でCSSを書くしかなさそうな感じでした。
<!-- layouts/default.vue -->
<template>
<v-app>
<div id="loading">
<v-progress-circular indeterminate />
</div>
<v-content>
<nuxt />
</v-content>
</v-app>
</template>
<style lang="scss">
#loading {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
z-index:9999;
position: fixed;
background-color: rgba(#000, 0.5);
}
</style>
<!-- pages/index.vue -->
<template>
<v-container class="text-center my-12">
<v-btn color="secondary">
Submit
</v-btn>
</v-container>
</template>
この時点で、とりあえずこんな画面ができました👍
2. スピナーの表示/非表示を切り替えられるようにする
これは普通に v-if
を使えばOKです。
<!-- layouts/default.vue -->
<template>
<v-app>
<div v-if="loading" id="loading">
<v-progress-circular indeterminate />
</div>
<v-content>
<nuxt />
</v-content>
</v-app>
</template>
<script>
export default {
data: () => ({
loading: true
})
}
</script>
<style lang="scss">
/* 略 */
</style>
あとはこの loading
の値を切り替えられるようにすれば完成できそうですね。
3. pages側からlayout側の状態を変更する
pages側からlayout側の状態を変更するシンプルな方法がパッと分からずググったのですが、「素直にVuexを使いましょう」的な情報が多い印象でした。(参考)
ただ、今回はそんな本格的な状態管理がしたいわけではなかったので、もう少し粘ってググってみたところ、こちらの記事 などで紹介されている EventBus方式 を使うのが手っ取り早そうということが分かりました。
EventBus方式というのが公式な呼び名なのかはよく分かりません。この記事 あたりが提唱元っぽいですが、詳細は不明です😅
具体的には、グローバルなVueオブジェクトの $emit()
と $on()
を通して、データの受け渡しをするというものです。
Nuxt.jsの場合は、 $nuxt
というグローバルなVueオブジェクトがデフォルトで存在しているので、これを使えば簡単に実装できます。
つまり、pages側で this.$nuxt.$emit()
を、layout側で this.$nuxt.$on()
を使うことで、イベントを通してデータの受け渡しができるというわけです👍
<!-- layouts/default.vue -->
<template>
<!-- 略 -->
</template>
<script>
export default {
data: () => ({
loading: false
}),
created () {
this.$nuxt.$on('toggleLoading', (loading) => {
this.loading = loading
})
}
}
</script>
<style lang="scss">
/* 略 */
</style>
<!-- pages/index.vue -->
<template>
<v-container class="text-center my-12">
<v-btn color="secondary" @click="submit">
Submit
</v-btn>
</v-container>
</template>
<script>
export default {
methods: {
submit () {
this.toggleLoading(true)
setTimeout(() => {
this.toggleLoading(false)
}, 3000)
},
toggleLoading (loading) {
this.$nuxt.$emit('toggleLoading', loading)
}
}
}
</script>
- layout側
-
created
フック でthis.$nuxt.on()
を使ってローディング状態を受け取るためのイベントりすなを登録
-
- pages側
- ボタンクリックおよびローディング終了のタイミングで
this.$nuxt.$emit()
を使ってローディング状態(表示/非表示の真偽値)を送る
- ボタンクリックおよびローディング終了のタイミングで
という実装になっています。
これで無事完成しました!🙌
最終的なコード
改めて、最終的なコードを載せておきます✋
<!-- layouts/default.vue -->
<template>
<v-app>
<div v-if="loading" id="loading">
<v-progress-circular indeterminate />
</div>
<v-content>
<nuxt />
</v-content>
</v-app>
</template>
<script>
export default {
data: () => ({
loading: false
}),
created () {
this.$nuxt.$on('toggleLoading', (loading) => {
this.loading = loading
})
}
}
</script>
<style lang="scss">
#loading {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
z-index:9999;
position: fixed;
background-color: rgba(#000, 0.5);
}
</style>
<!-- pages/index.vue -->
<template>
<v-container class="text-center my-12">
<v-btn color="secondary" @click="submit">
Submit
</v-btn>
</v-container>
</template>
<script>
export default {
methods: {
submit () {
this.toggleLoading(true)
setTimeout(() => {
this.toggleLoading(false)
}, 3000)
},
toggleLoading (loading) {
this.$nuxt.$emit('toggleLoading', loading)
}
}
}
</script>
まとめ
- Vuetifyの
v-progress-circular
にCSSを少し書き足せば、いい感じのローディング画面の表現が作れる - Nuxt.jsのレイアウト機能を使っている場合に、pages側からlayout側の状態を変更する(ローディング画面の表示/非表示を切り替える)には、
this.$nuxt.$emit()
とthis.$nuxt.$on()
を使ったEventBus方式が便利
Discussion