SPAのサイトを実装するうえで、ルーティングを切り替えることでモーダルを表示する実装をしたいことがあるのですが、Nuxt3で実装できるか試してみました。
フレームワークにはNuxt UIを利用しています。
まずは、基本のapp.vue
app.vue
<template>
<div>
<NuxtLink v-bind:to="{ name: 'modal' }" class="text-blue-500"
>モーダルトップ</NuxtLink
>
<ul class="list-disc pl-6">
<li>
<NuxtLink
v-bind:to="{ name: 'modal-id', params: { id: 1 } }"
class="text-blue-500"
>モーダルサブ1</NuxtLink
>
</li>
<li>
<NuxtLink
v-bind:to="{ name: 'modal-id', params: { id: 2 } }"
class="text-blue-500"
>モーダルサブ2</NuxtLink
>
</li>
<li>
<NuxtLink
v-bind:to="{ name: 'modal-id', params: { id: 3 } }"
class="text-blue-500"
>モーダルサブ3</NuxtLink
>
</li>
</ul>
<NuxtPage />
</div>
</template>
ただたんに、NuxtLinkで指定のモーダルを表示するリンクと、モーダルコンポーネントを入れるNuxtPageを記載しただけです。
この後、pagesディレクトリにモーダル表示用のコンポーネントを入れていきますが、それだけ入れるとモーダルを表示しない状態にすることができないので、「/pages/index.vue」には下記のように空のコンポーネントを入れておきます(もし、index.vueを置かなくても実装できる方法があれば教えてほしいです)。
/pages/index.vue
<template></template>
その他、pagesディレクトリには下記のファイルを置いておく。
/pages/modal/index.vue
<script setup>
const isOpen = ref(false);
const router = useRouter();
// 表示されたらすぐ開く
onMounted(() => {
isOpen.value = true;
});
const emit = defineEmits();
const onTransitionEnd = (ev) => {
if (!isOpen.value) {
// モーダルが閉じたら、URLをルートに変更
router.push({ path: "/" });
}
};
</script>
<template>
<UModal v-model="isOpen" @transitionend="onTransitionEnd">
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3
class="text-base font-semibold leading-6 text-gray-900 dark:text-white"
>
モーダルトップ
</h3>
<UButton
color="gray"
variant="ghost"
icon="i-heroicons-x-mark-20-solid"
class="-my-1"
@click="isOpen = false"
/>
</div>
</template>
<ul class="list-disc pl-6">
<li>
<NuxtLink
:to="{ name: 'modal-id', params: { id: 1 } }"
class="text-blue-500"
>サブモーダル1</NuxtLink
>
</li>
<li>
<NuxtLink
:to="{ name: 'modal-id', params: { id: 2 } }"
class="text-blue-500"
>サブモーダル2</NuxtLink
>
</li>
<li>
<NuxtLink
:to="{ name: 'modal-id', params: { id: 3 } }"
class="text-blue-500"
>サブモーダル3</NuxtLink
>
</li>
</ul>
</UCard>
</UModal>
</template>
/pages/modal/[id.vue]
<script setup lang="ts">
const isOpen = ref(false);
const router = useRouter();
// 表示されたらすぐ開く
onMounted(() => {
isOpen.value = true;
});
const route = useRoute();
const onTransitionEnd = (ev: TransitionEvent) => {
if (!isOpen.value) {
// モーダルが閉じたら、URLをルートに変更
router.push({ path: "/" });
}
};
</script>
<template>
<UModal v-model="isOpen" @transitionend="onTransitionEnd">
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3
class="text-base font-semibold leading-6 text-gray-900 dark:text-white"
>
サブモーダル(ID:{{ route.params.id }})
</h3>
<UButton
color="gray"
variant="ghost"
icon="i-heroicons-x-mark-20-solid"
class="-my-1"
@click="isOpen = false"
/>
</div>
</template>
<NuxtLink :to="{ name: 'modal' }" class="text-blue-500"
>トップに戻る</NuxtLink
><br />
<NuxtLink
:to="{ name: 'modal-id', params: { id: +route.params.id + 1 } }"
class="text-blue-500"
>次のモーダル</NuxtLink
>
</UCard>
</UModal>
</template>
実装したサンプルは下記。
Nuxt3のpagesにモーダルコンポ―ネントを入れたサンプル/
難しいことはしてないです。
しいていうなら、onMountedで描画されたらすぐにモーダルを表示するようにしているのと、モーダルが閉じたら「router.push({ path: “/” });」というようにルーティングをルートに戻してるというぐらいです。
この辺りは本来、コンポーザブル関数や、pages用の共通モーダルコンポーネントを実装して実装したほうがいいかもしれないです。
モーダルが閉じる時の判断は、Nuxt UIのModalに、モーダルが閉じた時のイベントがないので、transitionendイベントでアニメーションが終了した時に、モーダル表示フラグがfalseかどうかで閉じたかどうかを判断しています。これぐらいのイベントはあってほしいなと思いました。
なお、上記実装ではモーダルから別のモーダルに切り替わる際、切り替え前のモーダルはタグが無くなってしまうので、閉じる際のアニメーションは表示されません。最初は、切り替え前のモーダルが閉じるアニメーションを行うと同時に、切り替え後のモーダルが開くアニメーションを表示するようにしようと思ったのですが、UModalコンポーネントが別の場所で描画される(Teleport機能で実装されてる?)ようでうまくいきませんでした。
フレームワークを使わず、モーダルの機能自体を自作で実装すればできるかもしれないですが、時間がかかりそうなのでそこまでやってないです。
本題とはずれますが、今回使ったNuxt UIではCSSにTailwind CSSを使っているのですが、Tailwind CSSではデフォルトのスタイルが無くなってしまうんですね…。前から気になってはいたのですが、レイアウトの実装にはなかなか骨の折れる作業が必要そうだなと感じました…。

コメント