title: 星星背景
description: 一个使用分层运动和随机星星位置实现视差动画效果的星空背景。
navBadges:
  - value: 新
    type: lime
---

```vue
<template>
  <div
    class="relative h-96 w-full border border-zinc-800 rounded-xl overflow-clip flex flex-col items-center justify-center gap-4"
  >
    <StarsBackground
      :factor="0.05"
      :speed="50"
      star-color="#fff"
    />
  </div>
</template>

```

## API 接口

| 属性名       | 类型            | 默认值                           | 描述                                               |
| ------------ | --------------- | -------------------------------- | -------------------------------------------------- |
| `factor`     | `number`        | `0.05`                           | 鼠标视差移动的倍数。                               |
| `speed`      | `number`        | `50`                             | 星层垂直循环动画的基础速度（单位：秒）。           |
| `transition` | `SpringOptions` | `{ stiffness: 50, damping: 20 }` | 鼠标移动响应的弹性物理动画配置，确保运动流畅自然。 |
| `starColor`  | `string`        | `"#fff"`                         | 星星的颜色。支持任何合法的 CSS 颜色值。            |
| `class`      | `string`        | `—`                              | 可选，容器 div 的附加类。可用于设置 z-index 等。   |

> 💡 此组件包装了一个插槽（slot），你可以在背景上放置其他 UI 元素。

## 使用 CLI 安装

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

## 手动安装

```vue
<template>
  <div
    :class="
      cn(
        'relative size-full overflow-hidden bg-[radial-gradient(ellipse_at_bottom,_#262626_0%,_#000_100%)]',
        props.class,
      )
    "
    @mousemove="handleMouseMove"
  >
    <motion.div :style="{ x: springX, y: springY }">
      <!-- Star Layer 1 -->
      <motion.div
        class="absolute top-0 left-0 w-full h-[2000px]"
        :animate="{ y: [0, -2000] }"
        :transition="starLayer1Transition"
      >
        <div
          class="absolute bg-transparent rounded-full"
          :style="{
            width: '1px',
            height: '1px',
            boxShadow: boxShadow1,
          }"
        />
        <div
          class="absolute bg-transparent rounded-full top-[2000px]"
          :style="{
            width: '1px',
            height: '1px',
            boxShadow: boxShadow1,
          }"
        />
      </motion.div>

      <!-- Star Layer 2 -->
      <motion.div
        class="absolute top-0 left-0 w-full h-[2000px]"
        :animate="{ y: [0, -2000] }"
        :transition="starLayer2Transition"
      >
        <div
          class="absolute bg-transparent rounded-full"
          :style="{
            width: '2px',
            height: '2px',
            boxShadow: boxShadow2,
          }"
        />
        <div
          class="absolute bg-transparent rounded-full top-[2000px]"
          :style="{
            width: '2px',
            height: '2px',
            boxShadow: boxShadow2,
          }"
        />
      </motion.div>

      <!-- Star Layer 3 -->
      <motion.div
        class="absolute top-0 left-0 w-full h-[2000px]"
        :animate="{ y: [0, -2000] }"
        :transition="starLayer3Transition"
      >
        <div
          class="absolute bg-transparent rounded-full"
          :style="{
            width: '3px',
            height: '3px',
            boxShadow: boxShadow3,
          }"
        />
        <div
          class="absolute bg-transparent rounded-full top-[2000px]"
          :style="{
            width: '3px',
            height: '3px',
            boxShadow: boxShadow3,
          }"
        />
      </motion.div>
    </motion.div>

    <!-- Slot for child content -->
    <slot />
  </div>
</template>

<script setup lang="ts">
import type { SpringOptions } from "motion-v";
import { cn } from "@/lib/utils";
import { motion, useMotionValue, useSpring } from "motion-v";
import { computed, onMounted, ref, watch } from "vue";

interface StarsBackgroundProps {
  factor?: number;
  speed?: number;
  transition?: SpringOptions;
  starColor?: string;
  class?: string;
}

const props = withDefaults(defineProps<StarsBackgroundProps>(), {
  factor: 0.05,
  speed: 50,
  transition: () => ({ stiffness: 50, damping: 20 }),
  starColor: "#fff",
});

// For slot content
defineSlots();

function generateStars(count: number, starColor: string) {
  const shadows: string[] = [];
  for (let i = 0; i < count; i++) {
    const x = Math.floor(Math.random() * 4000) - 2000;
    const y = Math.floor(Math.random() * 4000) - 2000;
    shadows.push(`${x}px ${y}px ${starColor}`);
  }
  return shadows.join(", ");
}

const offsetX = useMotionValue(1);
const offsetY = useMotionValue(1);

const springX = useSpring(offsetX, props.transition);
const springY = useSpring(offsetY, props.transition);

function handleMouseMove(e: MouseEvent) {
  const centerX = window.innerWidth / 2;
  const centerY = window.innerHeight / 2;
  const newOffsetX = -(e.clientX - centerX) * props.factor;
  const newOffsetY = -(e.clientY - centerY) * props.factor;
  offsetX.set(newOffsetX);
  offsetY.set(newOffsetY);
}

const boxShadow1 = ref("");
const boxShadow2 = ref("");
const boxShadow3 = ref("");

onMounted(() => {
  boxShadow1.value = generateStars(1000, props.starColor);
  boxShadow2.value = generateStars(400, props.starColor);
  boxShadow3.value = generateStars(200, props.starColor);
});

// Watch for starColor changes
watch(
  () => props.starColor,
  (newColor) => {
    boxShadow1.value = generateStars(1000, newColor);
    boxShadow2.value = generateStars(400, newColor);
    boxShadow3.value = generateStars(200, newColor);
  },
);

const starLayer1Transition = computed(() => ({
  repeat: Infinity,
  duration: props.speed,
  ease: "linear",
}));

const starLayer2Transition = computed(() => ({
  repeat: Infinity,
  duration: props.speed * 2,
  ease: "linear",
}));

const starLayer3Transition = computed(() => ({
  repeat: Infinity,
  duration: props.speed * 3,
  ease: "linear",
}));
</script>

```

## 特性

- **动画星空背景**：渲染三个独立滚动的星层，具有不同的大小和密度。
- **视差效果**：响应鼠标移动，使用弹性物理动画增加层次感与真实感。
- **可自定义星星密度**：每一层使用不同数量的星星，呈现自然变化。
- **支持插槽**：作为视觉容器使用，允许在上层叠加前景 UI 或其他内容。
- **响应式设计**

## 致谢

- 移植自 [Animate UI](https://animate-ui.com/docs/backgrounds/stars)