title: 流星背景
description: A stunning animated starfield background with glowing and sharp trail effects.
---

```vue
<template>
  <div class="relative flex h-96 flex-col items-center justify-center">
    <FallingStarsBg
      class="bg-white dark:bg-black"
      :color="isDark ? '#FFF' : '#555'"
    />
    <div class="z-[1] flex items-center">
      <span class="text-6xl font-bold text-black dark:text-white">Inspira UI</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { useColorMode } from "@vueuse/core";

const isDark = computed(() => useColorMode().value == "dark");
</script>

```

## 通过 CLI 安装

```vue
<InstallationCli component-id="bg-falling-stars" />
```

## 手动安装

复制并粘贴以下代码：

```vue
<template>
  <canvas
    ref="starsCanvas"
    :class="cn('absolute inset-0 w-full h-full', $props.class)"
  ></canvas>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";
import { cn } from "@/lib/utils";

interface Star {
  x: number;
  y: number;
  z: number;
  speed: number;
}

const props = withDefaults(
  defineProps<{
    color?: string;
    count?: number;
    class?: string;
  }>(),
  {
    color: "#FFF",
    count: 200,
  },
);

const starsCanvas = ref<HTMLCanvasElement | null>(null);
let perspective: number = 0;
let stars: Star[] = [];
let ctx: CanvasRenderingContext2D | null = null;

onMounted(() => {
  const canvas = starsCanvas.value;
  if (!canvas) return;

  window.addEventListener("resize", resizeCanvas);
  resizeCanvas(); // Call it initially to set correct size

  perspective = canvas.width / 2;
  stars = [];

  // Initialize stars
  for (let i = 0; i < props.count; i++) {
    stars.push({
      x: (Math.random() - 0.5) * 2 * canvas.width,
      y: (Math.random() - 0.5) * 2 * canvas.height,
      z: Math.random() * canvas.width,
      speed: Math.random() * 5 + 2, // Speed for falling effect
    });
  }

  animate(); // Start animation
});

function hexToRgb() {
  let hex = props.color.replace(/^#/, "");

  // If the hex code is 3 characters, expand it to 6 characters
  if (hex.length === 3) {
    hex = hex
      .split("")
      .map((char) => char + char)
      .join("");
  }

  // Parse the r, g, b values from the hex string
  const bigint = parseInt(hex, 16);
  const r = (bigint >> 16) & 255; // Extract the red component
  const g = (bigint >> 8) & 255; // Extract the green component
  const b = bigint & 255; // Extract the blue component

  // Return the RGB values as a string separated by spaces
  return {
    r,
    g,
    b,
  };
}

// Function to draw a star with a sharp line and blurred trail
function drawStar(star: Star) {
  const canvas = starsCanvas.value;
  if (!canvas) return;

  ctx = canvas.getContext("2d");
  if (!ctx) return;

  const scale = perspective / (perspective + star.z); // 3D perspective scale
  const x2d = canvas.width / 2 + star.x * scale;
  const y2d = canvas.height / 2 + star.y * scale;
  const size = Math.max(scale * 3, 0.5); // Size based on perspective

  // Previous position for a trail effect
  const prevScale = perspective / (perspective + star.z + star.speed * 15); // Longer trail distance
  const xPrev = canvas.width / 2 + star.x * prevScale;
  const yPrev = canvas.height / 2 + star.y * prevScale;

  const rgb = hexToRgb();

  // Draw blurred trail (longer, with low opacity)
  ctx.save(); // Save current context state for restoring later
  ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.2)`;
  ctx.lineWidth = size * 2.5; // Thicker trail for a blur effect
  ctx.shadowBlur = 35; // Add blur to the trail
  ctx.shadowColor = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.8)`;
  ctx.beginPath();
  ctx.moveTo(x2d, y2d);
  ctx.lineTo(xPrev, yPrev); // Longer trail
  ctx.stroke();
  ctx.restore(); // Restore context state to remove blur from the main line

  // Draw sharp line (no blur)
  ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.6)`;
  ctx.lineWidth = size; // The line width is the same as the star's size
  ctx.beginPath();
  ctx.moveTo(x2d, y2d);
  ctx.lineTo(xPrev, yPrev); // Sharp trail
  ctx.stroke();

  // Draw the actual star (dot)
  ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)`;
  ctx.beginPath();
  ctx.arc(x2d, y2d, size / 4, 0, Math.PI * 2); // Dot with size matching the width
  ctx.fill();
}

// Function to animate the stars
function animate() {
  const canvas = starsCanvas.value;
  if (!canvas) return;

  ctx = canvas.getContext("2d");
  if (!ctx) return;

  ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas for each frame

  stars.forEach((star) => {
    drawStar(star);

    // Move star towards the screen (decrease z)
    star.z -= star.speed;

    // Reset star when it reaches the viewer (z = 0)
    if (star.z <= 0) {
      star.z = canvas.width;
      star.x = (Math.random() - 0.5) * 2 * canvas.width;
      star.y = (Math.random() - 0.5) * 2 * canvas.height;
    }
  });

  requestAnimationFrame(animate); // Continue animation
}

// Set canvas to full screen
function resizeCanvas() {
  const canvas = starsCanvas.value;
  if (!canvas) return;

  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
}
</script>

```

## API

| Prop 名称 | 类型     | 默认值   | 描述                                        |
| --------- | -------- | -------- | ------------------------------------------- |
| `color`   | `string` | `"#FFF"` | Color of the stars in the starfield.        |
| `count`   | `number` | `200`    | Number of stars displayed in the animation. |

## 功能特性

- **Dynamic Starfield**: Creates a 3D starfield effect with stars moving toward the viewer.
- **Glowing and Sharp Trail Effects**: Each star has a sharp line and a blurred, glowing trail.
- **Customizable**: Adjust the `color` of the stars and control the number of stars using the `count` prop.
- **Responsive Design**: Automatically adapts to the size of the canvas, ensuring a full-screen starfield effect.

## 感谢

- Inspired by 3D starfield simulations and trail effects in modern canvas animations.
- Credit to [Prodromos Pantos](https://github.com/prpanto) for porting the original component to Vue & Nuxt.