在现代网页应用中,徽章随处可见。你可以在通知计数器、状态标签以及功能标识上看到它们。
然而,大多数徽章都是静态的——它们只是静静地显示在那里,与页面融为一体。但一个设计精良、动画效果出色的徽章,能够在用户无需阅读任何文字的情况下,让他们了解到发生了什么。
在本教程中,你将使用 shadcn/ui、Tailwind CSS 以及 Framer Motion 来制作一个带有动画效果的“成功”徽章。这个徽章会拥有发光的顶部效果、一个会动态出现并跳动的勾选图标,以及以交错方式逐个显现的文字。
该组件来源于 Shadcn Space 徽章集合,并且使用了 Base UI 中的 Badge 组件。你只需通过一条 CLI 命令即可安装它,随后我们会一步步讲解每段代码的作用。
最终,你将通过以下步骤制作出一个带有动画效果的“成功”徽章:

  1. 使用 Shadcn CLI 从 Shadcn Space 安装 badge-07 组件

  2. 使用 motion.create() 将 shadcn/ui 中的 Badge 组件转化为可动画化的组件

  3. 通过绝对定位的方式添加分层径向渐变发光效果

  4. 为勾选图标添加缩放与旋转动画效果

  5. 使用交错效果的动画机制,分别控制标识中的每个字母

目录

先决条件

你需要准备以下内容:

  • 一个已初始化了 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 组件。你不能直接给它应用像 animateinitial 这样的动画属性。

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组件库,了解动画徽章如何融入完整的仪表盘和卡片布局中。


实时预览

08db3820-9f72-4ddb-a507-e33cdcda5fb8

关键概念回顾

概念 作用
motion.create(Component) 用于将任何React组件包装起来,使其能够接收Motion动画相关的属性。
Variants 在JSX外部定义的命名动画状态(如hiddenvisible),以便重复使用。
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集成方案也是一个值得考虑的选择。

资源链接

因此,

Comments are closed.