wui
组件

极光动态背景 Aurora Background

融合多重径向渐变光斑、柔和高斯模糊与无限平滑缓动的现代极光氛围背景组件。

第三方依赖 · motion

基础用法

极光光斑缓慢漂移;开启 interactive 后,三层光斑会以不同深度缓缓追随指针。背景跟随主题:亮色下是柔和的色雾,暗色下以滤色混合发光:

Loading…

安装与引入

通过 CLI 自动添加组件,或手动复制源码至项目中:

pnpm dlx @wui-design/cli@latest add @wui/aurora-background
安装基础依赖与动效库
pnpm add motion lucide-react class-variance-authority clsx tailwind-merge
复制组件源码到 components/ui/aurora-background.tsx
components/ui/aurora-background.tsx
"use client"

import * as React from "react"
import {
  motion,
  useMotionValue,
  useReducedMotion,
  useSpring,
  useTransform,
  type HTMLMotionProps,
  type MotionValue,
} from "motion/react"

import { cn } from "@/lib/utils"

export interface AuroraBackgroundProps extends Omit<
  HTMLMotionProps<"div">,
  "children"
> {
  /** Foreground content rendered above the aurora layers. */
  children?: React.ReactNode
  /** Three CSS colors used by the moving light fields. @default ["var(--chart-1)", "var(--chart-3)", "var(--chart-5)"] */
  colors?: [string, string, string]
  /** Seconds for one complete movement cycle. @default 18 */
  duration?: number
  /** Blur strength in pixels. @default 56 */
  blur?: number
  /** Let the light fields drift toward the pointer at different depths. @default false */
  interactive?: boolean
  /** Classes applied to every aurora layer. */
  layerClassName?: string
}

const layers = [
  {
    position: "-left-[18%] -top-[28%] h-[85%] w-[75%]",
    depth: 48,
    x: [0, 42, -24, 0],
    y: [0, -24, 30, 0],
  },
  {
    position: "-bottom-[34%] left-[24%] h-[90%] w-[72%]",
    depth: -64,
    x: [0, -36, 28, 0],
    y: [0, -24, 30, 0],
  },
  {
    position: "-right-[24%] top-[8%] h-[72%] w-[62%]",
    depth: 32,
    x: [0, 42, -24, 0],
    y: [0, 34, -18, 0],
  },
]

interface AuroraLayerProps {
  color: string
  index: number
  blur: number
  duration: number
  animated: boolean
  pointerX: MotionValue<number>
  pointerY: MotionValue<number>
  className?: string
}

function AuroraLayer({
  color,
  index,
  blur,
  duration,
  animated,
  pointerX,
  pointerY,
  className,
}: AuroraLayerProps) {
  const layer = layers[index]
  const x = useTransform(pointerX, (value) => value * layer.depth)
  const y = useTransform(pointerY, (value) => value * layer.depth)

  return (
    <motion.div className={cn("absolute", layer.position)} style={{ x, y }}>
      <motion.div
        data-slot="aurora-background-layer"
        className={cn(
          "size-full rounded-[50%] opacity-45 will-change-transform dark:opacity-60 dark:mix-blend-screen",
          className
        )}
        style={{
          background: `radial-gradient(closest-side, ${color}, transparent)`,
          filter: `blur(${blur}px)`,
        }}
        animate={
          animated
            ? { x: layer.x, y: layer.y, scale: [1, 1.12, 0.96, 1] }
            : undefined
        }
        transition={{
          duration: duration + index * 2,
          repeat: Infinity,
          ease: "easeInOut",
        }}
      />
    </motion.div>
  )
}

/** Layers slow, blurred color fields behind content. */
function AuroraBackground({
  children,
  colors = ["var(--chart-1)", "var(--chart-3)", "var(--chart-5)"],
  duration = 18,
  blur = 56,
  interactive = false,
  className,
  layerClassName,
  onPointerMove,
  onPointerLeave,
  ...props
}: AuroraBackgroundProps) {
  const reduceMotion = Boolean(useReducedMotion())
  const rawX = useMotionValue(0)
  const rawY = useMotionValue(0)
  const pointerX = useSpring(rawX, { stiffness: 36, damping: 18, mass: 1 })
  const pointerY = useSpring(rawY, { stiffness: 36, damping: 18, mass: 1 })

  return (
    <motion.div
      data-slot="aurora-background"
      className={cn(
        "bg-background relative isolate overflow-hidden",
        className
      )}
      onPointerMove={(event) => {
        if (interactive && !reduceMotion && event.pointerType !== "touch") {
          const rect = event.currentTarget.getBoundingClientRect()
          rawX.set((event.clientX - rect.left) / rect.width - 0.5)
          rawY.set((event.clientY - rect.top) / rect.height - 0.5)
        }
        onPointerMove?.(event)
      }}
      onPointerLeave={(event) => {
        rawX.set(0)
        rawY.set(0)
        onPointerLeave?.(event)
      }}
      {...props}
    >
      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0 -z-10"
      >
        {colors.map((color, index) => (
          <AuroraLayer
            key={index}
            color={color}
            index={index}
            blur={blur}
            duration={duration}
            animated={!reduceMotion}
            pointerX={pointerX}
            pointerY={pointerY}
            className={layerClassName}
          />
        ))}
      </div>
      <div className="relative z-10">{children}</div>
    </motion.div>
  )
}

export { AuroraBackground }

属性 Props

属性类型默认值说明
childrenReact.ReactNode—渲染在极光背景上层的主体内容(标题、按钮组、指标卡片等)。
colors[string, string, string]["var(--chart-1)", "var(--chart-3)", "var(--chart-5)"]驱动极光三层光斑的 CSS 颜色值(支持 Hex、RGB、HSL、OKLCH 或 CSS 变量),默认使用主题图表色。
durationnumber18极光完成一次完整位置漂移循环所需的总秒数。
blurnumber56极光光斑的高斯模糊半径(像素)。
interactivebooleanfalse光斑是否以不同深度缓慢追随指针,形成空间视差。触屏与“减少动态效果”下不生效。
layerClassNamestring—应用于极光单个浮动图层的额外 CSS 类名。
classNamestring—应用于极光最外层包装容器的 CSS 类名。

事件 Events

该组件为纯视觉背景容器组件,直接继承标准 React div 的原生事件属性。

使用场景与设计规范

AuroraBackground 适用于产品发布 Hero 区、AI 会话背景、VIP 定价卡片、创意案例展台:

  • 主题自适应:默认底色为 bg-background。亮色主题下光斑以普通混合呈现柔和色雾;暗色主题下切换为 mix-blend-screen 发光。需要固定深色舞台时,可通过 className 覆盖底色。
  • 微弱不抢戏:极光动画的 duration 推荐在 12 ~ 20 秒之间,缓慢流动的光效能提供高级的“呼吸感”,避免过快移动造成视觉疲劳。

场景示例

定价卡片

在单张卡片内使用更小的模糊半径与更短的周期:

Loading…

无障碍与交互 Accessibility

  • 自动静止降级:当用户开启 prefers-reduced-motion: reduce 时,光斑漂移动画将自动停滞,保留静态但依旧美观的高斯发光光晕。
  • 层级隔离:极光图层默认标记 pointer-events-none 与 aria-hidden="true",保证上层所有表单、按钮正常获取焦点。