1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Nuxt/VueのUIライブラリのInspira UIを利用し紙吹雪を簡単に実装する

Last updated at Posted at 2024-10-27

複雑なUIを簡単に実装できるInspira UIを利用し、紙吹雪のUIを実装してみました。

最終的な画面は以下の通り。

confetti.gif

他にも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を編集する。

nuxt.config.ts
export default defineNuxtConfig({
  devtools: { enabled: true },
  css: ['~/assets/css/main.css'],
  postcss: {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    },
  },
})

その後、tailwind.config.jsを以下の値で更新する

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を作成する

./assets/css/main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

上記で設定は完了となる。

app.vueを更新し、TailwindCSSが正しく設定されているか、動作確認を行う。

app.vue
<template>
  <h1 class="text-3xl font-bold underline">
    Hello world!
  </h1>
</template>

サーバーを起動し、Hello Worldの文字にアンダーラインが記載されていれば、OKとなる

npm run dev

スクリーンショット 2024-10-27 17.41.48.png

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を以下の内容に更新する

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も、以下の内容に更新する

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を新規作成する。

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のモジュール定義を追加する。

nuxt.config.ts
// 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を更新する。

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コンポーネントを作成し花吹雪の処理を実装する。

components/Confetti.vue
<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>

こちらで実装は完了。数秒おきに紙吹雪が表示されるようになる。

confetti.gif

紙吹雪の出し方を調整したい場合、以下の公式ドキュメントを参照

おわりに

他に利用可能なコンポーネントは、以下のURLから確認できる。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?