组件
边框光束 Border Beam
沿元素边框循环流动的一段细光束,用于强调推荐项、处理中状态或需要吸引注意的输入区域。
基础用法
把 BorderBeam 作为最后一个子元素放进带圆角的 relative 容器中,光束会贴合容器边框并继承其圆角。下例在点击发送后显示光束,表示内容正在生成:
Loading…
安装与引入
通过 CLI 自动添加组件,或手动复制源码至项目中:
pnpm dlx @wui-design/cli@latest add @wui/border-beam安装基础依赖与 Motion 动效库
pnpm add motion clsx tailwind-merge复制组件源码到
components/ui/border-beam.tsx"use client"
import * as React from "react"
import { motion, useReducedMotion, type Transition } from "motion/react"
import { cn } from "@/lib/utils"
export interface BorderBeamProps
extends Omit<React.ComponentProps<"div">, "children"> {
/** Length of the beam along the border, in pixels. @default 80 */
size?: number
/** Seconds for one full lap. @default 6 */
duration?: number
/** Seconds to offset the start, useful to stagger several beams. @default 0 */
delay?: number
/** Thickness of the beam; match the border width of the parent. @default 1 */
borderWidth?: number
/** Beam colour. Any CSS colour or variable. @default "var(--primary)" */
color?: string
/** Travel counter-clockwise. @default false */
reverse?: boolean
/** Starting position along the border, from 0 to 100. @default 0 */
initialOffset?: number
/** Overrides the looping transition. */
transition?: Transition
}
/**
* A short light beam that travels along the border of its parent. Place it as
* the last child of a `relative` element with a border radius; the beam sits
* exactly on the parent's border and inherits its radius.
*/
function BorderBeam({
size = 80,
duration = 6,
delay = 0,
borderWidth = 1,
color = "var(--primary)",
reverse = false,
initialOffset = 0,
transition,
className,
style,
...props
}: BorderBeamProps) {
const ref = React.useRef<HTMLDivElement>(null)
const reduceMotion = useReducedMotion()
const [radius, setRadius] = React.useState(0)
React.useEffect(() => {
const node = ref.current
if (!node) return
setRadius(Number.parseFloat(getComputedStyle(node).borderTopLeftRadius))
}, [])
if (reduceMotion) return null
const start = `${initialOffset}%`
const end = `${reverse ? initialOffset - 100 : initialOffset + 100}%`
return (
<div
ref={ref}
aria-hidden="true"
data-slot="border-beam"
className={cn("pointer-events-none absolute rounded-[inherit]", className)}
style={{
inset: -borderWidth,
padding: borderWidth,
maskImage: "linear-gradient(#000 0 0), linear-gradient(#000 0 0)",
maskClip: "content-box, border-box",
maskComposite: "exclude",
...style,
}}
{...props}
>
<motion.div
data-slot="border-beam-light"
className="absolute aspect-square"
style={{
width: size,
offsetPath: `rect(0 auto auto 0 round ${radius}px)`,
background: `linear-gradient(${reverse ? "to right" : "to left"}, ${color}, transparent)`,
}}
initial={{ offsetDistance: start }}
animate={{ offsetDistance: [start, end] }}
transition={{
repeat: Infinity,
ease: "linear",
duration,
delay: -delay,
...transition,
}}
/>
</div>
)
}
export { BorderBeam }
属性 Props
BorderBeam 支持以下配置属性,并继承原生 <div> 的其余 HTML 属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| size | number | 80 | 光束沿边框方向的长度(像素)。 |
| duration | number | 6 | 绕边框一圈的时长(秒)。 |
| delay | number | 0 | 起始进度偏移(秒),多条光束错开时使用,例如 `duration / 2`。 |
| borderWidth | number | 1 | 光束粗细,应与父元素的边框宽度一致。 |
| color | string | "var(--primary)" | 光束颜色,支持任意 CSS 颜色或变量。光束尾部自动渐隐为透明。 |
| reverse | boolean | false | 是否逆时针运动。 |
| initialOffset | number | 0 | 起始位置,取值 0–100,表示沿边框的百分比。 |
| transition | Transition | — | 覆盖默认的线性无限循环过渡。 |
使用场景与设计规范
- 表达状态而非装饰:光束最适合表达“正在处理”或“推荐选择”。静态页面中同时出现的光束不应超过一处。
- 保持克制:默认使用主题色且只有 1px 宽,尾部渐隐。不建议使用多色渐变或加粗光束,以免喧宾夺主。
- 放在边框上:光束会覆盖在父元素的边框位置,因此父元素不能设置
overflow: hidden,否则光束会被裁剪。需要裁剪内容时,请在内部再包一层。
场景示例
推荐套餐
两条相同速度、错开半圈的光束(delay={duration / 2})让推荐套餐的边框更均匀地流动:
Loading…
无障碍与交互 Accessibility
- 纯装饰元素:光束设置了
aria-hidden与pointer-events: none,不影响点击与读屏。其表达的状态(如“正在生成”)应同时通过文字呈现。 - 减少动态效果:开启
prefers-reduced-motion时不渲染光束,请确保推荐项仍有边框颜色、标签等静态区分。 - 浏览器支持:光束路径基于 CSS
offset-path: rect()与mask-composite,需要较新的 Chrome、Edge、Safari 与 Firefox。