Gutenberg Spinner 组件深度解析:@wordpress/components加载指示器的实现原理与迁移指南
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
Spinner是 Gutenberg 中用于提示"用户操作正在处理中"的轻量级反馈组件。本文以组件文档 packages/components/src/spinner/README.md 为主体,结合 组件源码 与 样式文件 深入讲解其渲染结构、动画实现与主题集成方式,并说明当前仓库中该组件的最新推荐用法与迁移路径。
一、组件定位与适用场景
按 组件文档 的定义,Spinner用于通知用户其触发的操作正在被处理。文档同时给出了明确的最佳实践边界:
Spinner 应当:向用户传达"请求正在处理中、且即将完成"的信号。
也就是说,Spinner 适合时长不可预估的异步操作(内容保存、媒体上传、服务端渲染等待等),不适合表示可精确计时的进度——那种场景应使用进度条类组件。
需要注意:该组件渲染的是纯装饰性 SVG,自身不携带任何文本或 ARIA 标签(role="presentation"、focusable="false"),因此无障碍信息必须由外层容器补充(例如配合 visually-hidden 文本或带aria-busy的父元素)。这一点从 源码中的 SVG 属性 可以直接确认。
二、基本用法
文档给出的标准用法:
import { Spinner } from '@wordpress/components'; function Example() { return <Spinner />; }组件通过forwardRef导出(见 index.tsx 第 56-58 行),因此支持将 ref 直接转发到内部 SVG 节点:
import { Spinner } from '@wordpress/components'; import { useRef } from '@wordpress/element'; function Example() { const ref = useRef<SVGSVGElement>(null); // ref.current 指向 <svg class="components-spinner" ...> return <Spinner ref={ref} />; }forwardRef的意义在于:当 Spinner 嵌入按钮、通知条等复合组件时,外层逻辑仍能拿到真实的 SVG DOM 节点,用于尺寸测量、动画控制或无障碍关联(如aria-describedby指向的隐藏提示文本)。
三、渲染结构:双图层 SVG 设计
UnforwardedSpinner 渲染一个viewBox="0 0 100 100"的 SVG,内含两个图层:
| 图层 | 元素 | 路径/参数 | 作用 |
|---|---|---|---|
| 轨道(track) | <circle> | cx="50" cy="50" r="50",即完整圆周 | 灰色底环,标示转动的完整轨迹 |
| 指示弧(indicator) | <path> | d="m 50 0 a 50 50 0 0 1 50 50" | 从顶点顺时针转 90° 的弧段,旋转形成"彗尾"效果 |
两个关键细节:
vectorEffect="non-scaling-stroke":两个图形都声明了该属性,其含义是描边宽度不参与 viewBox 缩放。因此 Storybook 故事 中注明"Spinner 可以缩放到任意尺寸,但描边宽度保持不变"——CustomSize故事正是通过style: { width: space(20), height: space(20) }将尺寸从 16px 放大到 20px 来验证这一行为。这解释了为什么组件在默认 16px 尺寸下使用 1.5px 描边不会随缩放失真。{...props}透传:组件签名{ className, ...props }: WordPressComponentProps<{}, 'svg', false>表明除className外的所有 SVG 原生属性都会透传给根元素,例如width/height可在调用侧直接覆盖默认的 16px。
// 通过透传属性自定义尺寸(描边宽度仍保持 1.5px) <Spinner width={24} height={24} />四、样式与动画实现
style.module.scss 采用 CSS Modules(styles.spinner等类名在编译时哈希隔离),核心样式如下:
.spinner { width: 16px; height: 16px; display: inline-block; margin: 5px 11px 0; position: relative; color: $components-color-accent; // 主题强调色 overflow: visible; opacity: 1; background-color: transparent; } .track, .indicator { fill: transparent; stroke-width: 1.5px; } .track { stroke: $components-color-gray-300; // 轨道:中性灰 } .indicator { stroke: currentColor; // 指示弧:取 .spinner 的 color stroke-linecap: round; // 圆头端点,弧段两端更柔和 transform-origin: 50% 50%; animation: spin 1.4s linear infinite both; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }实现上有三个值得注意的设计决策:
- 主题集成的两层机制:
.spinner的color取自$components-color-accent(主题强调色),.indicator的stroke则写为currentColor。这样指示弧颜色始终跟随组件色值,而overflow: visible保证旋转中的弧段在 16px 容器内不会被裁切。 - 动画参数:
1.4s linear infinite both——匀速旋转、无限循环,both填充模式确保动画在应用前/后都保持初始旋转状态,避免首帧闪烁。 - 动画不随
prefers-reduced-motion关闭:新版 UI 包样式 中有一条注释明确说明,旋转动画"即使用户设置了减少动态偏好也会保留",并引用 WCAG 2.2 关于pause-stop-hide准则的 Note 4——因为 Spinner 本身时长不可控,若静止显示会被误认为已完成,保留动画反而更符合该场景的可访问性要求。
五、Storybook 中的行为验证
Storybook 故事文件 提供了两个可交互案例:
- Default:直接渲染
<Spinner {...args} />,验证默认 16px 外观; - CustomSize:
args = { style: { width: space(20), height: space(20) } },验证"任意尺寸缩放 + 描边宽度不变"的承诺。
故事的元数据还揭示了组件的当前状态:
componentStatus: { status: 'not-recommended', whereUsed: 'global', notes: 'Use `Spinner` from `@wordpress/ui` instead.', },六、从@wordpress/components迁移到@wordpress/ui
当前仓库中该组件已被标记为not-recommended,官方建议迁移到@wordpress/ui包中的新版 Spinner。两个实现的对比:
| 维度 | @wordpress/components版 | @wordpress/ui版 |
|---|---|---|
| 源码位置 | packages/components/src/spinner/index.tsx | packages/ui/src/spinner/spinner.tsx |
| 类型签名 | WordPressComponentProps<{}, 'svg', false> | 标准ComponentProps<'svg'>,ref 明确为SVGSVGElement |
| 类名前缀 | components-spinner+ 模块哈希类 | 仅wp-ui层内的模块哈希类 |
| 尺寸 | 固定 16px(SCSS) | 设计令牌var(--wpds-dimension-size-2xs) |
| 轨道色 | $components-color-gray-300 | var(--wpds-color-background-track-neutral) |
| 指示弧色 | currentColor(强调色) | var(--wpds-color-background-thumb-brand) |
| CSS 组织 | 顶层 SCSS 模块 | @layer wp-ui内嵌套components层,见 style.module.css |
新版 Spinner 实现 保留了完全相同的几何结构(同样的 viewBox、circle + path、non-scaling-stroke、1.4s 旋转动画),主要变化是全面切换到 WPDS 设计令牌(--wpds-*CSS 变量)并使用 CSS@layer组织级联优先级,使其能参与新的设计系统主题体系。迁移方式即调整 import 来源:
// 旧 import { Spinner } from '@wordpress/components'; // 新 import { Spinner } from '@wordpress/ui';新版组件的行为同样有测试覆盖,见 spinner.jsdom.test.tsx。
七、小结
- Spinner 的本质是"灰色轨道 + 主题色弧段"的双图层 SVG,通过
transform: rotate的 1.4s 匀速循环动画形成旋转指示; vectorEffect="non-scaling-stroke"让组件可安全地按任意尺寸缩放而不改变 1.5px 描边,这一行为由 Storybook 的CustomSize故事专门验证;- 组件本身是装饰性元素(
role="presentation"),调用方需自行补充语义信息; - 在新代码中应优先使用
@wordpress/ui的 Spinner;@wordpress/components版本仍保留在全局使用范围(whereUsed: 'global'),但新接入请遵循not-recommended的标记完成迁移。
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考