# 边缘流光 Border Beam > 一道光沿着卡片边框慢慢绕圈,卡片外面带一点柔光。 分类:动效 · 角色:点缀(贴在某个元素上,一页两三个) [全部组件和流程](https://www.dachuiai.com/llms.txt) · [动效选型规则](https://www.dachuiai.com/guides/motion.md) ## 给 AI:用户把这个组件发给了你 1. 先看用户的项目:框架、页面类型、现在的风格、手机还是电脑为主。 2. 对照下面的「适合 / 不适合」判断合不合适。不合适就直接说,并从[动效选型规则](https://www.dachuiai.com/guides/motion.md)或[全部组件](https://www.dachuiai.com/llms.txt)里另挑一个。 3. 合适就按「装进项目」装好,示例里的文字和图片换成用户自己的。 4. 告诉用户用了什么、放在哪个文件、以后想改快慢、颜色、大小改哪个参数。 ## 什么时候用 - 适合:价格表里推荐的那一档、最想让人点的那张卡、AI 输入框。 - 不适合:每张卡都加(就没有重点了);浅色朴素风格。 - 适合的网站:SaaS、官网、活动页、电商 - 放在:卡片 - 手机上:好用:纯 CSS 动画。 - 性能代价:低:纯 CSS 动画。 - 常搭配:[聚光 Spotlight](https://www.dachuiai.com/c/spotlight.md)、[数字滚动 Count Up](https://www.dachuiai.com/c/count-up.md) ## 装进项目 - 写法:React 18 以上 + TypeScript + Tailwind CSS v4,一个文件就能用,不引用别的组件。 - 依赖:`npm i lucide-react`(lucide-react 只有最后的演示示例用到;不留示例的话,删掉对应的 import 就不用装) - 文件:原样存成 `border-beam.tsx`(比如 `src/components/border-beam.tsx`)。能跑命令就下载[原文](https://www.dachuiai.com/code/border-beam.tsx),比手抄准:`curl -fsSL -o src/components/border-beam.tsx https://www.dachuiai.com/code/border-beam.tsx`(Windows 写 `curl.exe`)。下载完看一眼第一行是 `import`,不是网页。 - 导出:`BorderBeam` 是可以直接用的部分;最后的 `export default` 是演示示例,只作参考。 - 图片:示例用了本站的 [/tex/grain.png](https://www.dachuiai.com/tex/grain.png)。换成用户自己的图;只想先看效果,可以从本站同样的路径下载,放进项目的 public 目录。 - 注意:radius 要和卡片的圆角一致。 - 保留代码里 `prefers-reduced-motion` 的处理:用户开了「减弱动态效果」时内容照样完整显示。 - Next.js(有 app 文件夹):文件第一行加 `'use client';`。 - 用 JavaScript 不用 TypeScript:Vite 和 Next.js 都能直接用 .tsx 文件;也可以删掉类型标注,改名 .jsx。 - 没有 Tailwind:先装 Tailwind CSS v4(Vite 项目 `npm i tailwindcss @tailwindcss/vite`,vite 配置的 plugins 里加 `tailwindcss()`,入口引入的那个 CSS 文件(一般是 `src/index.css`)第一行写 `@import "tailwindcss";`),或者把类名改写成项目用的样式。 - 已经装了 Tailwind v3:别为了一个组件升级。类名大多通用,少数 v4 写法要换,比如焦点框 `outline-2` 在 v3 里要写成 `outline outline-2`。 - 不是 React(Vue、Svelte、小程序、纯 HTML):照着代码的做法改写,时长、曲线、参数保持一样。Wix、有赞这类建站平台装不进代码,告诉用户去平台自带的动效设置里找。 ## 参数 | 参数 | 默认值 | 作用 | | --- | --- | --- | | `children` | | 包在里面的内容 | | `radius` | `24` | 圆角,px | | `duration` | `6` | 光绕一圈要几秒 | | `width` | `1.5` | 光带的粗细(px) | | `colorFrom` | `'#818CF8'` | 光带开头的颜色 | | `colorTo` | `'#F0ABFC'` | 光带结尾的颜色 | | `glow` | `true` | 外面是否带一圈柔光 | | `className` | `''` | 加在最外层的类名,用来调大小、位置、间距 | ## 你可以说 > 给推荐的那张价格卡片加一个 Border Beam 边缘流光:一道光沿着边框慢慢绕圈,卡片外面带一点柔光。 ## 完整代码 ```tsx import {useState, type CSSProperties, type ReactNode} from 'react'; import {Check} from 'lucide-react'; /* --------------------------------------------------------------- * BorderBeam 边缘流光 * 一道光沿着卡片边框绕圈。把卡片包在 里即可, * radius 要和卡片的圆角一致。 * ------------------------------------------------------------- */ type BorderBeamProps = { children: ReactNode; radius?: number; // 圆角,px duration?: number; // 转一圈的秒数 width?: number; // 光带粗细,px colorFrom?: string; colorTo?: string; glow?: boolean; // 卡片外的柔光 className?: string; }; export function BorderBeam({ children, radius = 24, duration = 6, width = 1.5, colorFrom = '#818CF8', colorTo = '#F0ABFC', glow = true, className = '', }: BorderBeamProps) { const vars = { '--bb-r': `${radius}px`, '--bb-w': `${width}px`, '--bb-dur': `${duration}s`, '--bb-from': colorFrom, '--bb-to': colorTo, borderRadius: radius, } as CSSProperties; return (
{glow && } {children}
); } const BEAM_CSS = ` @property --bb-angle { syntax: ''; inherits: false; initial-value: 0deg; } .bb-layer { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; background: conic-gradient(from var(--bb-angle), transparent 0deg 220deg, color-mix(in srgb, var(--bb-from) 35%, transparent) 268deg, var(--bb-from) 312deg, #fff 336deg, var(--bb-to) 346deg, transparent 360deg); animation: bb-spin var(--bb-dur) linear infinite; } .bb-ring { padding: var(--bb-w); -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; } .bb-ring-2 { opacity: .35; animation-delay: calc(var(--bb-dur) * -.5); } .bb-glow { inset: -2px; filter: blur(20px); opacity: .7; } @keyframes bb-spin { to { --bb-angle: 360deg; } } @media (prefers-reduced-motion: reduce) { .bb-layer { animation: none; --bb-angle: 150deg; } } `; /* ---------------- 示例:价格卡片,推荐的那张加流光 ---------------- */ const PLANS = [ {name: '基础版', note: '个人起步', monthly: 0, feats: ['3 个项目', '基础组件', '社区支持'], cta: '免费开始'}, {name: '专业版', note: '独立开发者', monthly: 99, feats: ['无限项目', '全部组件', 'AI 生成页面', '优先支持'], cta: '立即升级', hot: true}, {name: '团队版', note: '5 人以内小团队', monthly: 299, feats: ['多人协作', '权限管理', '专属客服'], cta: '联系我们'}, ]; export default function BorderBeamDemo() { const [yearly, setYearly] = useState(false); const price = (m: number) => (yearly ? Math.round(m * 0.8) : m); return (
{/* 背景:中间一团紫光 + 渐隐的网格 */}
{[['月付', false], ['年付 · 8 折', true]].map(([label, v]) => ( ))}
{PLANS.map((p) => { const card = (
{p.hot && ( <>
)}

{p.name}

{p.hot && ( 推荐 )}

{p.note}

¥ {price(p.monthly)} /月
    {p.feats.map((f) => (
  • {f}
  • ))}
); return p.hot ? ( {card} ) : (
{card}
); })}
); } ```