複雑なUIを簡単に実装できるInspira UIを利用し、紙吹雪のUIを実装してみました。
最終的な画面は以下の通り。
他にもInspira UIを利用することで、アニメーションリストや3Dカードなど、nxutでリッチなUI作ろうとしたときに簡単に実装ができそうでした。
インストールの際に、公式の手順とは異なる箇所がいくつかあったため、備忘録としてインストール手順から残しておきます。
最終的なコードは以下の通り
はじめかた
nuxtのプロジェクトを新規作成する。
npx nuxi@latest init inspira-ui-sample
Tailwind CSSの設定
Inspira UIの利用に必要なTailwindCSSの設定を行う。公式の手順を参考に進める。
必要ライブラリのインストール。
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
インストールが完了したら、nuxt.config.ts
を編集する。
export default defineNuxtConfig({
devtools: { enabled: true },
css: ['~/assets/css/main.css'],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
})
その後、tailwind.config.js
を以下の値で更新する
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
],
theme: {
extend: {},
},
plugins: [],
}
最後に./assets/css/main.css
を作成する
@tailwind base;
@tailwind components;
@tailwind utilities;
上記で設定は完了となる。
app.vue
を更新し、TailwindCSSが正しく設定されているか、動作確認を行う。
<template>
<h1 class="text-3xl font-bold underline">
Hello world!
</h1>
</template>
サーバーを起動し、Hello Worldの文字にアンダーラインが記載されていれば、OKとなる
npm run dev
Inspira UIの設定
Inspira UIの設定を行う。公式の手順を参考に進める。
必要をインストール
npm install -D @inspira-ui/plugins clsx tailwind-merge tailwindcss-animate class-variance-authority
npm install @vueuse/core @vueuse/motion
インストール完了後、各種設定ファイルの更新を行う。
まずtailwind.config.js
を以下の内容に更新する
/** @type {import('tailwindcss').Config} */
import animate from "tailwindcss-animate";
import { setupInspiraUI } from "@inspira-ui/plugins";
export default {
darkMode: "selector",
safelist: ["dark"],
prefix: "",
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
"./node_modules/flowbite/**/*.{js,ts}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
xl: "calc(var(--radius) + 4px)",
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [animate, setupInspiraUI],
};
assets/css/main.css
も、以下の内容に更新する
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
}
lib/utils.ts
を新規作成する。
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export type ObjectValues<T> = T[keyof T];
最後にnuxt.config.ts
に@vueuse/motion/nuxt
のモジュール定義を追加する。
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
modules: ["@vueuse/motion/nuxt"], // 追加
devtools: { enabled: true },
css: ["~/assets/css/main.css"],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
});
こちらでIsnpira UIの設定は完了。
紙吹雪のUIの実装
紙吹雪(Confetti)を利用する場合は、以下のライブラリを追加でインストールする必要がある。
npm install canvas-confetti
npm install -D @types/canvas-confetti
その後app.vue
を更新する。
<template>
<div
class="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg border bg-background md:shadow-xl"
>
<span
class="pointer-events-none whitespace-pre-wrap bg-gradient-to-b from-black to-gray-300/80 bg-clip-text text-center text-8xl font-semibold leading-none text-transparent dark:from-white dark:to-slate-900/10"
>
Confetti
</span>
<!-- Confetti component with ref -->
<Confetti
ref="confettiRef"
class="absolute top-0 left-0 z-0 size-full"
@mouseenter="fireConfetti"
/>
</div>
</template>
<script setup lang="ts">
const confettiRef = ref(null);
// Function to trigger confetti
const fireConfetti = () => {
confettiRef.value?.fire({});
};
</script>
Confettiコンポーネントを作成し花吹雪の処理を実装する。
<template>
<div>
<canvas ref="canvasRef" :class="$props.class"></canvas>
<slot />
</div>
</template>
<script setup lang="ts">
import confetti, {
GlobalOptions as ConfettiGlobalOptions,
Options as ConfettiOptions,
CreateTypes as ConfettiInstance,
} from "canvas-confetti";
type Api = {
fire: (options?: ConfettiOptions) => void;
};
type ConfettiProps = {
options?: ConfettiOptions;
globalOptions?: ConfettiGlobalOptions;
manualstart?: boolean;
class?: string;
};
const props = defineProps<ConfettiProps>();
const instanceRef = ref<ConfettiInstance | null>(null);
const canvasRef = ref<HTMLCanvasElement | null>(null);
// Confetti API
function fire(opts: ConfettiOptions = {}) {
instanceRef.value?.({ ...props.options, ...opts });
}
const api: Api = { fire };
provide("ConfettiContext", api);
// Initialize confetti when mounted
onMounted(() => {
if (canvasRef.value) {
instanceRef.value = confetti.create(canvasRef.value, {
...props.globalOptions,
resize: true,
});
if (!props.manualstart) {
fire();
}
}
});
// Cleanup when unmounted
onUnmounted(() => {
if (instanceRef.value) {
instanceRef.value.reset();
instanceRef.value = null;
}
});
defineExpose({
fire,
});
</script>
こちらで実装は完了。数秒おきに紙吹雪が表示されるようになる。
紙吹雪の出し方を調整したい場合、以下の公式ドキュメントを参照
おわりに
他に利用可能なコンポーネントは、以下のURLから確認できる。