news 2026/9/14 11:36:09

React Spectrum 进度指示三件套:ProgressBar、ProgressCircle 与 Meter 的 v3 API 设计及 v2 迁移指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React Spectrum 进度指示三件套:ProgressBar、ProgressCircle 与 Meter 的 v3 API 设计及 v2 迁移指南

React Spectrum 进度指示三件套:ProgressBar、ProgressCircle 与 Meter 的 v3 API 设计及 v2 迁移指南

【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum

本文围绕仓库中 specs/api/Progress.md 这份 API 设计规格展开,系统讲解 React Spectrum v3 中三个进度指示组件——ProgressBar(进度条)、ProgressCircle(环形进度圈)与Meter(计量表)的完整 Props 契约、默认值行为与无障碍实现,并完整保留原文档的 v2 → v3 迁移对照表,结合 useProgressBar Hook、ProgressBarBase 基础组件 等源码逐条印证每个 API 决策背后的命名规范与实现原理。读完本文,你将能够直接按 v3 API 编写这三个组件、把 v2 代码平滑迁移到 v3,并理解 Spectrum 为何如此拆分和命名这些 Props。

一、为什么是"三件套":组件拆分与 API 总览

规格文档开篇以 TypeScript 接口形式给出了三个组件的 v3 API 契约,这是整份文档的核心骨架:

interface ProgressBar { value?: number, minValue?: number, maxValue?: number, size?: 'S' | 'L', label?: ReactNode, 'aria-label'?: string, labelPosition?: 'top' | 'side', showValueLabel?: boolean, // true by default if label, false by default if not formatOptions?: Intl.NumberFormatOptions, // defaults to formatting as a percentage. valueLabel?: ReactNode, // custom value label (e.g. 1 of 4) variant?: 'overBackground', isIndeterminate?: boolean } interface ProgressCircle { value?: number, minValue?: number, maxValue?: number, size?: 'S' | 'M' | 'L', variant?: 'overBackground', isCentered?: boolean, isIndeterminate?: boolean } interface Meter extends ProgressBar { variant: 'positive' | 'warning' | 'critical' }

这段契约本身就体现了 Spectrum 的 API 设计准则(见 specs/api/Guidelines.md):

  • 组件拆分:Guidelines 中 "Splitting Components" 一节规定"当组件的选项不再适合放在一起时应拆分为独立组件"。v2 中variant="positive" | "warning" | "critical"本是ProgressBar的变体,但这些颜色变体表达的是"用户行为造成的量"(如存储占用告警),而非"系统操作进度",语义完全不同,因此 v3 将其拆分为独立的Meter组件——Meter extends ProgressBar正体现了"复用进度条的取值 API,叠加语义化变体"的拆分思路。
  • 方向无关命名labelPosition只接受'top' | 'side'而不接受'left'/'right',因为side在 RTL 布局下可自动翻转方向,这是 Guidelines 中 "Direction Agnostic Naming" 规则的直接落地。
  • 布尔状态前缀isisIndeterminate表示组件状态,按 "Boolean Props" 规则以is开头。
  • 约束型 Props 命名minValue/maxValue在名字尾部带上被约束的属性value,而非含糊的min/max,符合 "Prop Restrictions" 规则。

各组件的定位差异

组件语义底层实现
ProgressBar系统操作进度(下载、上传、处理),有/无确定进度均可ProgressBar.tsx
ProgressCircle同上,但用环形展示,常用于悬浮加载态ProgressCircle.tsx
Meter已知范围内的"量"(如磁盘容量),由用户行为决定而非系统操作Meter.tsx

Meter源码中的注释也明确了这一区分:"Meters are visual representations of a quantity or an achievement. Their progress is determined by user actions, rather than system actions."(Meter.tsx L29-L32)

二、ProgressBar:契约、默认值与底层 Hook

2.1 默认值与值域钳制

对照规格契约,ProgressBarBase 中定义了实际的默认值,读者可以据此确认各字段的取值行为:

let { value = 0, minValue = 0, maxValue = 100, size = 'L', label, showValueLabel = !!label, // 关键:有 label 默认显示数值,无 label 默认不显示 labelPosition = 'top', isIndeterminate = false, ... } = props; value = clamp(value, minValue, maxValue); // 越界值会被钳制

几个要点值得注意:

  1. showValueLabel的条件默认值:契约中注释 "true by default if label, false by default if not" 在源码中正是showValueLabel = !!label一行实现——只有给了可见标签时才默认展示百分比数值,避免无标签场景下孤悬的数字。
  2. value会被钳制(clamp):传入value: -1会得到0,传入value: 1000会得到maxValue。测试用例 ProgressCircle.test.js 中的 "clamps values to 0 / clamps values to 100" 两个it用例专门验证了这一点。
  3. 进度填充宽度的计算
let range = maxValue - minValue; let percentage = range === 0 ? 0 : (value - minValue) / range; barStyle.width = `${Math.round(percentage * 100)}%`;

即填充宽度是(value - minValue) / (maxValue - minValue)四舍五入到整数百分比,并特判了range === 0minValue === maxValue)时宽度为 0 的除零边界。

2.2 无障碍 Hook:useProgressBar

ProgressBar组件本体很薄(ProgressBar.tsx L24-L44),真正计算 ARIA 属性的逻辑全部在 useProgressBar:

value = clamp(value, minValue, maxValue); let range = maxValue - minValue; let percentage = range === 0 ? 0 : (value - minValue) / range; let formatter = useNumberFormatter(formatOptions); if (!isIndeterminate && !valueLabel) { let valueToFormat = formatOptions.style === 'percent' ? percentage : value; valueLabel = formatter.format(valueToFormat); } return { progressBarProps: mergeProps(domProps, { ...fieldProps, 'aria-valuenow': isIndeterminate ? undefined : value, 'aria-valuemin': minValue, 'aria-valuemax': maxValue, 'aria-valuetext': isIndeterminate ? undefined : (valueLabel as string), role: 'progressbar' }), labelProps };

这里印证了规格契约中formatOptionsvalueLabel两个 v3 新增字段的完整语义:

  • formatOptions默认是{ style: 'percent' }(契约注释 "defaults to formatting as a percentage"),此时数值标签对percentage(0~1 的比例)做格式化;若用户传入了其他Intl.NumberFormatOptions(如{ style: 'decimal', maximumFractionDigits: 2 }),则对原始value做格式化——这就是注释里 "others can also be supported" 的实现路径。
  • valueLabel用于 "1 of 4" 这类自定义数值文案:当提供了valueLabel时不再走 formatter,直接把该 ReactNode 作为aria-valuetext输出。
  • isIndeterminate时不输出aria-valuenowaria-valuetext,符合 WAI-ARIA 对不确定进度条的要求;测试 ProgressCircle.test.js 中 "handles indeterminate" 用例正是断言aria-valuenow属性不存在。
  • 标签元素使用labelElementType: 'span'而非<label>,因为进度条不是可聚焦的 HTML 表单控件(见 useProgressBar.ts L89-L94)。

2.3 渲染结构与数值标签的联动

ProgressBarBase.tsx L102-L132 渲染出label → valueLabel → track → fill的结构,其中数值标签直接复用 ARIA 侧算好的结果:

{showValueLabel && barProps && ( <div className={classNames(styles, 'spectrum-BarLoader-percentage')}> {barProps['aria-valuetext']} </div> )}

即视觉上显示的百分比文本与aria-valuetext保证一致。labelPosition="side"会切换到spectrum-BarLoader--sideLabel类,size="S"/"L"分别映射--small/--large修饰类。另外注意variant="overBackground"在 v3 源码中已标注@deprecated,推荐使用staticColor'white' | 'black')替代(ProgressBarBase.tsx L46-L52)——这是规格契约中variant字段的一个演进细节。

三、ProgressCircle:双掩膜旋转实现环形填充

ProgressCircle在取值契约上与ProgressBar相同(value/minValue/maxValue,默认 0/0/100,见 ProgressCircle.tsx L22-L45),差异在规格表中列出的两处:

  • size支持'S' | 'M' | 'L'三档,默认'M'(进度条只有两档,默认'L');
  • 新增isCenteredminValue/maxValue(迁移表见第五节)。

源码上最有趣的部分是它没有使用 SVG 弧线,而是用两块 CSS 掩膜旋转来"画出"任意角度的环形填充(ProgressCircle.tsx L94-L107):

let range = maxValue - minValue; let percentage = range === 0 ? 0 : ((value - minValue) / range) * 100; let angle; if (percentage > 0 && percentage <= 50) { angle = -180 + (percentage / 50) * 180; subMask1Style.transform = `rotate(${angle}deg)`; subMask2Style.transform = 'rotate(-180deg)'; } else if (percentage > 50) { angle = -180 + ((percentage - 50) / 50) * 180; subMask1Style.transform = 'rotate(0deg)'; subMask2Style.transform = `rotate(${angle}deg)`; }

原理可以推断为:一个 360° 圆环拆成上下两个半圆掩膜(DOM 中对应fillSubMask1/fillSubMask2两个子元素,见 ProgressCircle.tsx L134-L151)。进度在前半圈(0~50%)时只旋转掩膜 1;超过 50% 时掩膜 1 定格在 0°(盖满上半圈),掩膜 2 继续旋转覆盖下半圈。每块掩膜一次最多转 180°,正好对应各自半圆的范围。isIndeterminatetrue时不设置任何旋转,交给 Spectrum CSS 的spectrum-CircleLoader--indeterminate类播放旋转动画。

可访问性方面,ProgressCircle复用同一个useProgressBarHook(第 90 行传入{...props, value}),因此 ARIA 输出与进度条完全一致;并且在非生产环境下,若既无aria-label也无aria-labelledby会主动告警(ProgressCircle.tsx L109-L113),进度条侧的等价告警在 ProgressBarBase.tsx L96-L100。

四、Meter:继承 ProgressBar 契约并叠加语义变体

规格定义interface Meter extends ProgressBar { variant: 'positive' | 'warning' | 'critical' }。实际实现上,Meter.tsx 复用了ProgressBarBase作为渲染骨架,只替换了 ARIA Hook 与修饰类:

export interface SpectrumMeterProps extends SpectrumProgressBarBaseProps { variant?: 'informative' | 'positive' | 'warning' | 'critical'; // @default 'informative' } export const Meter = React.forwardRef(function Meter(props, ref) { let {variant = 'informative', ...otherProps} = props; const {meterProps, labelProps} = useMeter(props); return ( <ProgressBarBase {...otherProps} barProps={meterProps} barClassName={classNames(styles, { 'is-positive': variant === 'positive', 'is-warning': variant === 'warning', 'is-critical': variant === 'critical' })} /> ); });

两个源码层面的补充说明:

  1. 默认变体是'informative'(中性蓝),规格表列出的positive/warning/critical是三个强调状态;informative时不加任何修饰类,走 Spectrum CSS 默认样式。
  2. ARIA role 使用双值"meter progressbar":useMeter 内部先调用useProgressBar,再把 role 改写为meter progressbar,源码注释解释了原因——部分浏览器(注释中提到 Chrome 会自动回退、Firefox 当时不支持 meter、Safari 13+ 支持)对meterrole 支持不一,写两个 role 值可以让屏幕阅读器在新旧环境下都能得到正确语义。

Meter 的测试见 Meter.test.js 与 SSR 场景下的 Meter.ssr.test.js。

五、v2 → v3 迁移对照表(完整继承自规格文档)

5.1 ProgressBar Changes

v2v3Notes
<Progress><ProgressBar>
size="M"size="L"spectrum calls it large, not medium
labelPosition="left"labelPosition="side"rtl support
labelPosition="bottom"-not supported.
showPercentshowValueLabeldefault changed to true if label is specified, false if not.
-numberFormatteradded. default is percentage, but others can also be supported.
-valueLabelcustom value label, e.g. "1 of 4"
-isIndeterminateadded
minminValue
maxmaxValue
variant="positive"<Meter variant="positive">
variant="warning"<Meter variant="warning">
variant="critical"<Meter variant="critical">

逐条解读这些改动的动机:

  • size="M"size="L":Spectrum 设计系统把进度条的两档尺寸命名为 small/large,v2 的 "M" 在 v3 词汇表中对应 "L",直接改名避免语义错位。
  • labelPosition="left""side"side是方向无关术语,RTL 下自动指右侧,这正是第五节开头提到的 Guidelines 规则;"bottom"则被整体取消支持。
  • showPercentshowValueLabel:改名后语义更泛——显示的不只是百分比(受formatOptions影响),默认值逻辑也变为"有 label 默认开、无 label 默认关",与 ProgressBarBase 中showValueLabel = !!label完全对应。
  • numberFormatter→ 源码中的formatOptions:规格表中 v3 新增项写作numberFormatter("added. default is percentage, but others can also be supported"),当前源码将其演进为formatOptions?: Intl.NumberFormatOptions,直接透传给Intl.NumberFormat,默认{ style: 'percent' },与规格意图一致且接口更标准。
  • min/maxminValue/maxValue:约束型 Props 带上被约束属性名,消除歧义。
  • variant三兄弟整体迁往<Meter>:语义拆分,ProgressBar保留variant: 'overBackground'(叠加在彩色背景上的变体,现已标记 deprecated,推荐staticColor)。

5.2 ProgressCircle Changes

v2v3Notes
<Wait><ProgressCircle>
variant="indeterminate"isIndeterminate
centeredisCentered
-minValueadded
-maxValueadded
  • <Wait><ProgressCircle>:组件从"等待指示器"升级为通用进度指示器,因此可以像ProgressBar一样表达确定进度(value/minValue/maxValue)。
  • variant="indeterminate"isIndeterminate:不确定态是组件"状态"而非"视觉风格",按 Guidelines 以is前缀的布尔属性表达,variant一词留给真正的视觉变体('overBackground')。
  • centeredisCentered:同类改名,布尔状态统一is前缀。
  • 新增minValue/maxValue:与ProgressBar的取值契约对齐,源码中 ProgressCircle 的取值段 与进度条完全同构。

六、安装与使用:入口、导出与最小示例

三个组件通过@adobe/react-spectrum主包导出,导出映射见 exports/ProgressBar.ts 与 exports/Meter.ts;独立子包入口 packages/@react-spectrum/progress/src/index.ts 则额外导出了可复用的ProgressBarBase与类型,供二次封装时复用同一渲染骨架。

import {Provider, ProgressBar, ProgressCircle, Meter} from '@adobe/react-spectrum'; export function Example() { return ( <Provider> {/* 确定进度:0-100,默认按百分比显示数值标签 */} <ProgressBar label="正在上传照片…" value={72} /> {/* 自定义范围与数值文案:"1 of 4" 风格 */} <ProgressBar minValue={1} maxValue={4} value={2} valueLabel="第 2 步,共 4 步" label="安装进度" /> {/* 不确定进度:不渲染 aria-valuenow,播放滑动动画 */} <ProgressBar aria-label="正在加载" isIndeterminate /> {/* 环形加载器,需自行提供可达性标签 */} <ProgressCircle aria-label="处理中" value={30} size="L" /> {/* 计量表:磁盘空间告警 */} <Meter label="存储空间" value={82} variant="critical" /> </Provider> ); }

上述示例中的行为均可在源码中找到依据:value={72}会被钳制后映射为 72% 填充宽度;valueLabel同时驱动视觉文本与aria-valuetextisIndeterminatearia-valuenowundefinedMetervariant="critical"落到is-critical修饰类。

配套资源

  • 官方文档页:ProgressBar.mdx、ProgressCircle.mdx、Meter.mdx
  • 交互示例(Storybook):stories/progress、stories/meter
  • 视觉回归用例:chromatic/progress、chromatic/meter
  • 测试:ProgressBar.test.js(含负值钳制、缺失 aria-label 告警、自定义 DOM props 透传等用例)、ProgressCircle.test.js、Meter.test.js

七、小结

specs/api/Progress.md 用不到 70 行定义清晰划定了 v3 进度指示组件族的 API 边界:ProgressBar承载系统操作进度(含不确定态与可定制数值格式化)、ProgressCircle提供环形形态与三档尺寸、Meter从进度条契约中派生出面向"量"的语义变体。对照源码可以确认,这份契约并非纸面设计——每个字段(从showValueLabel的条件默认值、clamp钳制,到role="meter progressbar"的浏览器兼容策略)都能在 ProgressBarBase、useProgressBar、useMeter 中找到逐行对应的实现,且被 test/progress 与 test/meter 下的测试用例持续验证。对于仍在 v2 上的项目,按第五节两张迁移表逐项替换(组件改名、min/max补全为minValue/maxValuevariant强调态迁往Metercentered/indeterminateis前缀)即可完成平滑升级。

【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 11:35:56

遗传算法优化电力系统功率损耗的MATLAB实现

1. 项目背景与核心问题 在电力系统运行中&#xff0c;输电线路的功率损耗是影响电网经济性的关键因素。传统调度方法通常基于固定规则或简单优化算法&#xff0c;难以应对用电需求的动态变化。本项目采用遗传算法&#xff08;Genetic Algorithm, GA&#xff09;对发电站的用电需…

作者头像 李华
网站建设 2026/9/14 11:31:34

手机购物商城HTML源码实战:从静态页面到可交互demo

简介&#xff1a;手机购物商城网站HTML源码是一套面向移动端电商场景的前端页面集合&#xff0c;专为入门级前端学习者、移动端网页开发者以及需要完成课程设计或毕业设计的人群准备&#xff0c;可帮助快速搭建商城UI框架&#xff0c;也能作为项目二次开发的起点。资源包为RAR格…

作者头像 李华
网站建设 2026/9/14 11:30:42

基于Matlab的RRT路径规划与PID跟踪控制实现

1. 移动机器人路径跟踪系统概述 在自动化仓储物流、无人驾驶等场景中&#xff0c;移动机器人的自主导航能力至关重要。一个完整的导航系统通常包含环境感知、路径规划和运动控制三大模块。本文将重点探讨基于Matlab实现的路径规划与跟踪系统&#xff0c;该系统采用RRT算法进行全…

作者头像 李华
网站建设 2026/9/14 11:30:34

SpringBoot校园市场系统快速部署与答辩实战指南

简介&#xff1a;这是一套基于SpringBoot开发的校园市场平台完整毕业设计项目源码&#xff0c;面向计算机相关专业本科生及Java初学者&#xff0c;专为大作业、毕业设计与项目实战练习打造。资源包含可直接编译运行的后端Java代码、前端VueJS页面、数据库SQL脚本及配套配置文件…

作者头像 李华
网站建设 2026/9/14 11:29:09

如何用 uv 从源码安装 NeMo Speech 并验证 ASR 与 TTS 集合可用

如何用 uv 从源码安装 NeMo Speech 并验证 ASR 与 TTS 集合可用 【免费下载链接】Speech A scalable generative AI framework built for researchers and developers working on Large Language Models, Multimodal, and Speech AI (Automatic Speech Recognition and Text-to…

作者头像 李华