在现代网页应用中,徽章随处可见。你可以在通知计数器、状态标签以及功能标识上看到它们。
然而,大多数徽章都是静态的——它们只是静静地显示在那里,与页面融为一体。但一个设计精良、动画效果出色的徽章,能够在用户无需阅读任何文字的情况下,让他们了解到发生了什么。
在本教程中,你将使用 shadcn/ui、Tailwind CSS 以及 Framer Motion 来制作一个带有动画效果的“成功”徽章。这个徽章会拥有发光的顶部效果、一个会动态出现并跳动的勾选图标,以及以交错方式逐个显现的文字。
该组件来源于 Shadcn Space 徽章集合,并且使用了 Base UI 中的 Badge 组件。你只需通过一条 CLI 命令即可安装它,随后我们会一步步讲解每段代码的作用。
最终,你将通过以下步骤制作出一个带有动画效果的“成功”徽章:
-
使用 Shadcn CLI 从 Shadcn Space 安装
badge-07组件 -
使用
motion.create()将 shadcn/ui 中的Badge组件转化为可动画化的组件 -
通过绝对定位的方式添加分层径向渐变发光效果
-
为勾选图标添加缩放与旋转动画效果
-
使用交错效果的动画机制,分别控制标识中的每个字母
目录
先决条件
你需要准备以下内容:
-
一个已初始化了 shadcn/ui 的 Next.js 项目。
-
Tailwind CSS 已经配置完成。
-
已安装
motion库:`npm install motion` -
已安装
lucide-react库:`npm install lucide-react` -
具备基本的 TypeScript 和 React 知识。
你将构建什么
在这个教程中,我们将构建一个包含三个可移动部分的独立动画徽章:
├── MotionBadge(轮廓样式、圆角设计、青绿色边框)
│ ├── 发光效果层 → 位于顶部边框上方的3个放射状渐变效果
│ ├── 检查圈图标 → 入场时进行缩放和旋转动画,使用easeOutBack过渡效果
│ └── 字符显示部分 → 以交错方式逐渐出现,使用easeOutCubic过渡效果
安装完成后,该组件的文件会保存在以下路径:
components/
└── shadcn-space/
└── badge/
└── badge-07.tsx
如何安装该组件
Shadcn UI提供了一个包含各种可立即使用的组件的注册库。你可以使用Shadcn CLI将它们添加到你的项目中,就像添加任何标准的shadcn/ui组件一样。
在运行任何命令之前,请先查看入门指南或CLI页面以了解详细的设置步骤。
你也可以观看以下视频教程来学习如何操作:
根据你使用的包管理器,运行相应的命令进行安装:
pnpm
pnpm dlx shadcn@latest add @shadcn-space/badge-07
npm
npx shadcn@latest add @shadcn-space/badge-07
Yarn
yarn dlx shadcn@latest add @shadcn-space/badge-07
Bun
bunx --bun shadcn@latest add @shadcn-space/badge-07
注意: badge-07使用的是Base UI框架中的徽章组件版本。注册库中同时提供了Radix和Base UI两种版本的组件,本教程介绍的是Base UI版本。
组件结构
以下是该组件的完整代码结构。先整体阅读一遍,然后下面的每一步都会详细解释其中的具体部分。
{/* 顶部发光效果 */}
>
>
{char}
<\/motion/span>
))}
<\/span>
<\/MotionBadge>
);
};
export default SuccessBadgeDemo;
现在,让我们一步步来分析这个过程。
步骤 1:设置导入模块
'use client'这一指令表明该组件属于 Next.js 应用程序路由器中的客户端组件。Motion 动画是在浏览器中执行的,而不是在服务器端,因此这个指令是必需的。
motion/react是用于导入 Motion v11 及更高版本的模块路径。如果你的项目使用的是较低版本,应该使用 framer-motion 这个导入路径。Variants 类型是 TypeScript 中的一种辅助类型,用于为带有名称的动画状态对象定义类型。
cn()是一个实用工具类,所有基于 shadcn/ui 开发的项目都会包含这个类。它可以将 Tailwind CSS 的样式与条件逻辑结合起来进行处理。
步骤 2:定义字母动画的变体
const LETTER_VARIANTS: Variants = {
hidden: { y: -14, opacity: 0 },
visible: (i: number) => ({
y: 0,
opacity: 1,
transition: {
delay: i * 0.038,
duration: 0.35,
ease: [0.215, 0.61, 0.355, 1],
},
}),
};
每个字母在开始时都会位于其最终位置的上面 14 像素处,并且是完全透明的。当组件被渲染到页面上时,这些字母会移动到 y: 0 的位置,并且透明度变为 1。
delay: i * 0.038 这个公式用于控制字母出现的顺序。字母 0 没有延迟,字母 1 等待 38 毫秒再出现,字母 2 等待 76 毫秒,依此类推。这样,字母们就会从左向右依次出现。
ease参数的值为 [0.215, 0.61, 0.355, 1],表示动画效果为 easeOutCubic。这种效果意味着动画开始时速度较快,然后逐渐减速,从而使每个字母都有一个自然的过渡效果,而不会突然停止。
visible函数接受一个 custom 参数。当你在 motion.span 组件上使用 custom={i} 时,Motion 会使用这个索引值来调用 visible 函数。因此,每个字母都会根据自己的索引值来计算延迟时间。
无障碍提示:为了尊重那些不喜欢动画效果的用户,你可以从 motion/react 中导入 useReducedMotion 这个模块。当它返回 true 时,就可以忽略字母出现的延迟效果。
步骤 3:为 Badge 组件添加动画效果
const MotionBadge = motion.create(Badge);
来自 shadcn/ui 的 Badge 组件是一个标准的 React 组件。你不能直接给它应用像 animate 或 initial 这样的动画属性。
motion.create()这个函数可以用来包裹任何 React 组件,从而生成一个新的组件版本,这个新版本能够接受所有的 Motion 动画相关属性。最终得到的 MotionBadge 组件,其行为与原来的 Badge 完全相同,只不过现在它可以进行动画展示了。
每当您想要使用 Motion 来为自定义组件或第三方库组件添加动画效果时,都可以使用这种模式。
步骤 4:创建发光层
<motion.span
aria-hidden
animate={{ opacity: 0.55 }}
transition={{ duration: 0.45 }}
className="pointer-events-none absolute -top-2 left-[10%] right-[10%] h-4 blur bg-[radial-gradient(ellipse_80%_100%_at_50%_100%,rgba(45,212,191,0.95)_0%,transparent_70%)]"
/>
<motion.span
aria-hidden
animate={{ opacity: 0.75 }}
transition{{{ duration: 0.45 }}
className="pointer-events-none absolute -top-1 left-[22%] right-[22%] h-2 blur-sm bg-[radial-gradient(ellipse_70%_100%_at_50%_100%,rgba(45,212,191,0.85)_0%,transparent_70%)]"
/>
<motion.span
aria-hidden
animate={{ opacity: 0.9 }}
transition{{{ duration: 0.45 }}
className="pointer-events-none absolute top-0 left-[28%] right-[28%] h-px bg-[radial-gradient(ellipse_40%_50%_at_50%_50%,rgba(45,212,191,0.95)_0%,transparent_100%)]"
/>
这三个 `` 元素堆叠在徽章边框的上方。每个元素的宽度都比下面的元素窄,不透明度也更低:
| 层 | 位置 | 宽度 | 模糊效果 | 最终不透明度 |
|---|---|---|---|---|
| 外层 | -top-2 |
80% | blur |
0.55 |
| 中间层 | -top-1 |
56% | blur-sm |
0.75 |
| 内层线条 | top-0 |
44% | none | 0.90 |
最内层的元素高度仅为 1 像素(使用 `h-px`),且没有模糊效果。这样一来,徽章边框处的发光部分就能呈现出清晰、明亮的边缘;而外两层则负责营造出柔和的渐变效果。
这三层元素都添加了 `aria-hidden` 属性,因为它们纯粹是用于装饰目的的。屏幕阅读器会跳过这些元素。而 `MotionBadge` 上的 `overflow-visible` 类属性使得这些 `` 元素能够显示在组件边界之外而不会被裁剪掉。
步骤 5:为图标添加动画效果
<motion.span
initial={{ scale: 0.35, opacity: 0, rotate: -25 }}
animate={{ scale: 1, opacity: 1, rotate: 0 }}
transition{{{ duration: 0.32, ease: [0.175, 0.885, 0.32, 1.275] }}
className="flex h-4 w-4 shrink-0 items-center justify-center"
>
<CheckCircle size={16} strokeWidth={2} className="text-teal-400" />
</motion.span>
这个图标的初始大小为原始大小的 35%,是不可见的,并且旋转了 25 度。在动画开始时,它会恢复到正常大小并且停止旋转。
`ease` 参数的值 `[0.175, 0.885, 0.32, 1.275]` 表示的是 `easeOutBack` 曲线。与 `easeOutCubic` 不同,这种曲线在到达目标值之前会稍微超过目标值,然后再迅速回到目标值。因此,图标看起来就像是“弹跳”到正确位置上的。虽然这种效果很微妙,但它能让图标显得更加生动、有质感。
在包装元素上应用shrink-0这个样式,可以防止图标在弹性容器内部被压缩。
步骤6:为每个字母添加动画效果
<span className="inline-flex overflow-hidden leading-none">
{label.split("").map((char, i) => (
<motion.span
key={i}
custom={i}
variants={LETTER_VARIANTS}
initial="hidden"
animate="visible"
className="inline-block whitespace-pre"
>>
{char}
</motion/span>
))}
</span>
label.split("")这个操作会将"Success"拆分成["S", "u", "c", "c", "e", "s", "s"]。每个字符都会对应一个独立的motion.span》元素。
variants={LETTER_VARIANTS}这个代码将每个motion/span》元素与步骤2中定义的动画状态关联起来。custom={i}则将字符的索引传递给visible函数,这样每个字母就能根据自身的索引来执行不同的动画效果。
这里有两个非常重要的Tailwind CSS类:
-
在包装元素上应用
overflow-hidden,可以防止字母从上方滑入时被裁剪掉。如果不使用这个样式,字母在完全显示之前就会出现在徽章的外边。 -
每个
motion.span》元素都需要设置inline-block属性,这样translateY动画才能正常生效。默认情况下,CSS的变换效果并不适用于内联元素。
如何在你的应用中使用它
你可以在项目的任何地方导入并渲染SuccessBadgeDemo组件:
// app/page.tsx
import SuccessBadgeDemo from "@/components/shadcn-space/badge/badge-07";
export default function Page() {
return (
<div className="flex items-center justify-center min-h-screen">
<>SuccessBadgeDemo />
</div>
);
}
这个组件是自包含的,它拥有自己的动画状态、主题样式以及发光效果,因此不需要任何额外的属性。
如何自定义这个组件
你可以通过将"Success"替换为任意字符串来更改显示的文本。由于该组件会自动将输入的字符串拆分成各个字符,因此字母动画效果依然会自动生效。
如果想要创建一个蓝色的“Verified”版本,只需要修改三处内容:边框颜色、发光效果的渐变颜色以及图标。以下是更新后的完整组件代码:
'use client'
import { motion, type Variants } from "motion/react";
import { ShieldCheck } from "lucide-react";
import { Badge } from "@components/ui/badge";
import { cn } from "@lib/utils";
const LETTER_VARIANTS: Variants = {
hidden: { y: -14, opacity: 0 },
visible: (i: number) => ({
y: 0,
opacity: 1,
transition: {
delay: i * 0.038,
duration: 0.35,
ease: [0.215, 0.61, 0.355, 1],
},
}),
};
const MotionBadge = motion.create(Badge);
const VerifiedBadgeDemo = () => {
const label = "Verified";
return (
<MotionBadge
variant="outline"
className={cn(
"relative h-auto cursor-default overflow-visible rounded-full",
"gap-2 px-3 py-2",
"bg-background backdrop-blur-md",
"text-foreground text-sm font-medium leading-none",
"border-blue-400/25",
)}
>
<motion.span aria-hidden animate={{ opacity: 0.55 }} transition={{ duration: 0.45 }}
className="pointer-events-none absolute -top-2 left-[10%] right-[10%] h-4 blur bg-[radial-gradient(ellipse_80%_100%_at_50%_100%,rgba(96,165,250,0.95)_0%,transparent_70%)]"
/>
<motion.span aria-hidden animate={{ opacity: 0.75 }} transition={{ duration: 0.45 }}
className="pointer-events-none absolute -top-1 left-[22%] right-[22%] h-2 blur-sm bg-[radial-gradient(ellipse_70%_100%_at_50%_100%,rgba(96,165,250,0.85)_0%,transparent_70%)]"
/>
<motion.span aria-hidden animate={{ opacity: 0.9 }} transition={{ duration: 0.45 }}
className="pointer-events-none absolute top-0 left-[28%] right-[28%] h-px bg-[radial-gradient(ellipse_40%_50%_at_50%_50%,rgba(96,165,250,0.95)_0%,transparent_100%)]"
/>
<motion.span
initial={{ scale: 0.35, opacity: 0, rotate: -25 }}
animate={{ scale: 1, opacity: 1, rotate: 0 }}
transition={{ duration: 0.32, ease: [0.175, 0.885, 0.32, 1.275] }}
className="flex h-4 w-4 shrink-0 items-center justify-center"
>
<ShieldCheck size={16} strokeWidth={2} className="text-blue-400" />
{label.split("").map((char, i) => (
{char}
))}
);
};
export default VerifiedBadgeDemo;
与原始版本相比,唯一的变化在于:徽章上使用了border-blue-400/25这一样式;光晕渐变效果采用了rgba(96, 165, 250, ...)的配色方案(在Tailwind中对应blue-400);图标使用了ShieldCheck形状;而图标类名也被设置为text-blue-400。
若要调整元素显示的间隔时间,只需修改LETTER_VARIANTS中的延迟倍数即可:
delay: i * 0.06, // 显示间隔较慢
delay: i * 0.02, // 显示间隔较快
您还可以查看Shadcn组件库,了解动画徽章如何融入完整的仪表盘和卡片布局中。
实时预览

关键概念回顾
| 概念 | 作用 |
|---|---|
motion.create(Component) |
用于将任何React组件包装起来,使其能够接收Motion动画相关的属性。 |
Variants |
在JSX外部定义的命名动画状态(如hidden、visible),以便重复使用。 |
custom={i} + 变体函数 |
将针对每个元素的特定值传递给变量解析函数,从而实现动态过渡效果。 |
delay: i * 0.038 |
延迟计算公式:每个元素的延迟时间会根据其索引值逐渐增加。 |
easeOutCubic [0.215, 0.61, 0.355, 1] |
动画开始迅速,随后平稳减速,字母效果呈现得非常自然。 |
easeOutBack [0.175, 0.885, 0.32, 1.275] |
动画开始时会略微超调,随后迅速恢复到目标状态,图标效果会显得更加突出。 |
| 三种叠加的径向渐变效果 | 外部光晕效果宽广且柔和,内部线条则狭窄而清晰。 |
在徽章上使用overflow-visible |
这使得光晕效果能够延伸到组件边界之外。 |
总结
通过本教程,您从零开始制作了一个包含分层光晕效果、可弹跳的图标以及延迟显示的字母动画的完整徽章组件。该组件的所有部分都使用了Shadcn主题中已有的元素,因此无需额外配置即可直接应用于任何项目中。
您还可以在Shadcn Space网站上浏览更多Shadcn组件库中的组件,将相同的动画效果应用到其他UI元素上。如果您在使用外部服务或工具,那么Shadcn MCP集成方案也是一个值得考虑的选择。
资源链接
-
Shadcn Space徽章组件:提供了包括“待处理”“失败”等在内的各种类型的徽章。
-
Shadcn Space使用指南:介绍了如何将Shadcn CLI与第三方注册系统结合使用。
-
Motion文档:关于
motion/react的官方文档。 -
Lucide React:本教程中使用的图标库。
因此,




