はじめに
アウトプットとして記事を書きましょうと指示があり、何を書こうか悩んだ結果一番最初に作成したトースト表示について書こうと決めギリギリで書き始めました。
ただ実装すると、複数のトーストを表示させたとき上に重なってしまったり、自然と消えてしまうので、本記事では重なったり消えてしまったりしないトーストの実装方法についてまとめてみました。
トースト通知とは
画面端にひょこっと現れて数秒で消える、短いお知らせメッセージのことです。
パン焼き器から焼きあがったトーストが飛び出す様子に似ていることからこの名前が付いたそうです。
今回はvuetify3のv-snackbarを使ってトースト通知を実装していきます。
v-snackbarとは
画面に一時的なメッセージを表示させるためのvuetifyコンポーネントのこと
基本の実装
まずは、Vuetifyで最もシンプルなトースト表示。
<template>
<v-btn @click="show = true">表示</v-btn>
<v-snackbar v-model="show">こんにちは!</v-snackbar>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
課題: これでは通知が届くたびに上書きされ、ユーザーが情報を見逃す可能性があります。
解決策:スタック型トーストの実装
通知を配列で管理し、インデックスに応じて表示位置を動的にずらします。
状態管理を切り出し、どこからでも通知を呼べるようにします。
import { reactive } from 'vue'
const toasts = reactive<any[]>([])
export const useToast = () => {
const hideToast = (id: number) => {
const i = toasts.findIndex(t => t.id === id)
if (i !== -1) toasts.splice(i, 1)
}
return { toasts, hideToast }
}
次が本質的な実装部分です。
<script setup lang="ts">
const { toasts, hideToast } = useToast()
const onSnackbarClose = (toastId: number, value: boolean) => {
if (!value) hideToast(toastId)
}
</script>
<template>
<v-snackbar
v-for="(toast, i) in toasts"
:key="toast.id"
:model-value="toast.show"
:color="toast.color"
location="top right"
timeout="3000"
:style="{ top: `${i * 75 + 16}px`}
"@update:model-value="onSnackbarClose(toast.id, $event)"
>
<v-icon v-if="toast.icon" :icon="toast.icon" class="mr-2"></v-icon>
{{ toast.text }}
<template v-if="toast.showClose" #actions>
<v-btn @click="hideToast(toast.id)"> close </v-btn>
</template>
</v-snackbar>
</template>
工夫したポイント
-
:style="{ top: \${i * 75 + 16}px}"`を使うことで、配列の順番に応じて表示位置を下にずらしています。これにより通知が重ならなくなりました -
@update:model-valueをフックにすることで、タイムアウトによる自動消去と配列からの削除を同期させました -
アイコンやボタンの有無を toast オブジェクトごとに切り替えられるようにして、汎用性を高めました
さいごに
入社後初めての案件で、何が分からないのかもわからない状態でしたが最近では少しずつ分かることが増えたような気がします。(すぐ忘れてしまうんですが…)
今回アウトプット記事を書いてみて、そういえばこんな感じだったなと思い出すこともできていい機会になったなと思います。
もっとがんばろうととても思いました。