组件
闪光按钮 Shiny Button
带有连续平滑流动扫光效果的高亮展示型按钮,用于强化页面核心行动点(CTA)与转化入口。
基础用法
最简单的闪光按钮形态。内置流动的斜向光斑扫描动画:
Loading…
安装与引入
通过 CLI 自动添加组件,或手动复制源码至项目中:
pnpm dlx @wui-design/cli@latest add @wui/shiny-button安装动效依赖与工具库
pnpm add motion clsx tailwind-merge复制组件源码到
components/ui/shiny-button.tsx"use client"
import * as React from "react"
import { motion, useReducedMotion, type HTMLMotionProps } from "motion/react"
import { cn } from "@/lib/utils"
export interface ShinyButtonProps extends HTMLMotionProps<"button"> {
/** Seconds for one full light sweep across the button. @default 3 */
speed?: number
/** Pause between sweeps, in seconds. @default 1.2 */
gap?: number
// Narrows motion's children type (which allows MotionValue) back to ReactNode.
children?: React.ReactNode
}
/**
* A "fancy" template component: a filled button with a light sweep that
* continuously glides across it. Effect components like this are opt-in and are
* allowed to be more opinionated than the core UI — but they still gate motion
* on `prefers-reduced-motion`. Copy it as a starting point for your own effects.
*/
function ShinyButton({
className,
speed = 3,
gap = 1.2,
children,
...props
}: ShinyButtonProps) {
const reduceMotion = useReducedMotion()
return (
<motion.button
data-slot="shiny-button"
whileTap={reduceMotion ? undefined : { scale: 0.97 }}
className={cn(
"relative inline-flex h-10 shrink-0 items-center justify-center gap-2 overflow-hidden whitespace-nowrap rounded-lg border border-primary/20 bg-primary px-6 text-sm font-medium text-primary-foreground shadow-sm outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className
)}
{...props}
>
<span className="relative z-10 inline-flex items-center gap-2">
{children}
</span>
<motion.span
aria-hidden
data-slot="shiny-button-sheen"
className="pointer-events-none absolute inset-y-0 left-0 w-1/2"
style={{
background:
"linear-gradient(110deg, transparent 25%, var(--shine) 50%, transparent 75%)",
}}
initial={{ x: "-120%" }}
animate={reduceMotion ? undefined : { x: "220%" }}
transition={{
duration: speed,
ease: [0.45, 0, 0.25, 1],
repeat: Infinity,
repeatDelay: gap,
}}
/>
</motion.button>
)
}
export { ShinyButton }
属性 Props
ShinyButton 接受以下配置属性,并继承 motion.button 的全部 HTML 属性与动画属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| speed | number | 3 | 光斑扫过整个按钮所需的周期时间(秒)。数值越小扫光越快。 |
| gap | number | 1.2 | 每次扫光完成后的间隔静止时间(秒)。 |
| children | React.ReactNode | — | 按钮内部渲染的内容,通常包含文字或图标。 |
| disabled | boolean | false | 是否禁用按钮。禁用时将自动隐藏扫光图层并阻止交互。 |
| className | string | — | 应用于按钮容器的额外 CSS 类名,可用于自定义尺寸、圆角与背景色。 |
事件 Events
ShinyButton 继承原生按钮及 Motion 包装层的所有事件回调:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| onClick | (event: React.MouseEvent<HTMLButtonElement>) => void | — | 用户点击或通过键盘激活按钮时触发。 |
| onFocus | (event: React.FocusEvent<HTMLButtonElement>) => void | — | 按钮获得焦点时触发。 |
| onBlur | (event: React.FocusEvent<HTMLButtonElement>) => void | — | 按钮失去焦点时触发。 |
| onHoverStart | (event: MouseEvent, info: EventInfo) => void | — | 鼠标指针移入按钮区域时触发(Motion 回调)。 |
| onHoverEnd | (event: MouseEvent, info: EventInfo) => void | — | 鼠标指针离开按钮区域时触发(Motion 回调)。 |
使用场景与设计规范
ShinyButton 属于展示型组件(Fancy 系列),具有极强的视觉吸引力。
- 使用场景:
- 核心转化 CTA:官网首页首屏的“免费试用”、“即刻体验”、“快速部署”等主行动点。
- 付费与升级通道:价格方案对比卡片中的“升级专业版”或限时折扣引导。
- 新功能与早鸟公告:引导用户体验实验性功能的醒目标签按钮。
- 克制使用原则:
- 同一视口中切忌大面积平铺多个闪光按钮,否则会分散用户注意力造成严重视觉疲劳。
- 普通表单、密集表格操作列以及次要取消操作请使用基础的
Button。
- 动效与色彩适配:
- 光效层使用半透明渐变叠加在背景之上,在深色(Dark Mode)与浅色(Light Mode)模式下均能保持优雅的对比度。
场景示例
定价与方案升级对比
在定价卡片中将核心推荐方案的操作按钮升级为 ShinyButton,快速建立视觉层级差异:
Loading…
内测招募模块
与标题、说明和次要操作组合,构建克制但醒目的转化模块:
Loading…
尺寸与形状变体
通过 Tailwind 类名轻松调整尺寸与圆角(如胶囊型 Pill):
Loading…
无障碍与交互 Accessibility
- 动效降级保障:组件内部使用
useReducedMotion监听操作系统偏好。当用户开启了“减少动态效果”时,扫光动画自动关闭,按压缩放动效自动移除,组件退化为静态高对比度按钮。 - 键盘焦点:支持标准的 Tab 聚焦以及 Enter / Space 激活,焦点轮廓清晰可见且不会被扫光层遮盖。
- 非侵入式光斑:扫光层标记为
aria-hidden="true"并配置pointer-events-none,绝不干扰文字选区与屏幕阅读器朗读。