Figma 评分与星级评价系统(Rate & Star Rating Component System)组件架构实战:从 0.5 半星遮罩裁切、10 态悬浮/聚焦流转矩阵、Variables 步进状态机到 Dev Mode 无障碍代码交付
在电商商品评价、酒店差旅预订、内容社区打分以及企业级客户满意度(CSAT / NPS)调研等业务场景中,评分控件(Rate & Star Rating Component) 是使用频次最高、转化漏斗最关键的核心交互单元之一。五颗微小的星形图元不仅直接承载着用户对产品或服务的主观口碑量化,更直接影响着其他潜在消费者的购买决策。
然而,在日常的设计系统搭建与跨团队协作中,评分组件往往是“漏洞百出”的重灾区:
- 0.5 半星(Half-Star)几何建模缺陷:大量设计师使用粗暴的布尔运算(Boolean Subtract)将五角星对半裁剪,这不仅导致在不同分辨率下矢量顶点破面或产生亚像素缝隙,更使得组件在响应式缩放时发生非等比扭曲;
- 交互状态机断层(Hover & Focus 缺失):只绘制了静态的 1~5 颗满星,缺少用户鼠标移入时的实时悬浮点亮预览(Hover Feedback)、移动端按压反馈(Pressed),以及无障碍场景下不可或缺的键盘焦点轮廓环(Focus-Visible Ring);
- 评语文字(Rating Label)联动脱节:分值对应的说明文案(如“1星-非常不满意”、“5星-超出预期”)在设计稿中往往是死文本,无法根据当前所选星级在 Auto Layout 容器 中自适应换行或动态联动;
- 高保真原型成本失控:以往要制作一个支持点击打分的原型,需要设计师手工复制排列数十个画框并在连接线之间反复穿梭,维护成本极高;
- 研发交付无障碍与工程语义割裂:前端开发在 Dev Mode 开发模式 中提取出的只是一堆并排的 SVG 矢量 path,难以映射为符合 WAI-ARIA 国际标准的单选组语义,亦不清楚该使用 CSS 遮罩(Masking)还是分段 SVG 实现。
本文将深入拆解 Star Rating 的原子图元结构,实战 0.5 半星防畸变裁剪,构建正交的 10 阶状态机矩阵,并借助 Figma Variables 变量打造单画框自运行评分原型与规范化前端代码交付。
评分控件的形态解耦与选型矩阵 #
评分控件并非仅仅由 5 颗星组成,它通常包含图元导轨、动态分值回显与评语辅助说明三大部分:
| 模块构成 | 视觉与交互职责 | 关键设计规范与 Token | 常见变体属性 | 容错与防御策略 |
|---|---|---|---|---|
| 原子星元(Star Unit) | 承载基础打分单位,支持空心、半填充、全填充 | 标准 16px / 20px / 24px,默认间距 4px~8px | State: Empty / Half / Full | 触控热区必须扩充至 44px 以上,防误触 |
| 分值文案(Score Text) | 数字精确回显(如 4.5 或 9.2/10) | 14px~16px SemiBold,必须开启 Tabular Nums 等宽 | ShowScore: true / false | 始终锁定等宽排印,避免从 4.9 变到 5.0 时文字跳跃 |
| 评语标签(Rating Label) | 直观传达情感倾向(如“极佳”、“待改善”) | 12px~14px 次要文本色,支持气泡 Tooltip 悬浮展现 | ShowLabel: true / false | 配置 Truncate 或自适应宽度,避免长文案挤出容器 |
| 只读展示态(Read-only) | 仅用于展示历史均分,禁止任何指针交互与点击 | 视觉对比度适度弱化,光标设为 default | Interactive: false | 屏蔽键盘 Tab 键聚焦,在代码层声明 aria-readonly="true" |
| 禁用态(Disabled) | 灰显且不可操作(如问卷已截止或无权限) | 全局透明度降低至 40%~50%,图元统一填充禁用灰 | Disabled: true | 光标设为 not-allowed,阻止一切事件派发 |
0.5 半星(Half Star)的高精度几何建模与防锯齿实战 #
制作 0.5 半星是评分组件搭建中最考验几何建模规范的一步。
传统布尔裁剪的陷阱 #
许多初学者会直接绘制一个五角星,再画一个覆盖其右半部分的矩形,执行 Subtract selection(布尔减除)。这种方式存在两大隐患:
- 布尔运算产生的复合图元在跨团队库发布或导出为 SVG 时,容易由于坐标舍入导致中轴线多出 0.5px 的半透明残留黑线;
- 当组件需要支持 16px、20px、24px 动态等比缩放时,布尔形状的矢量控制点极易在渲染引擎中产生亚像素模糊(Sub-pixel blur)。
最佳工程实践:双层叠加与 Auto Layout 容器裁切(Clip Content) #
最为稳定、整洁且最契合前端 CSS 渲染原理的方法是遮罩容器裁切法:
[Star-Unit Container] (Frame, 20x20px, Constraints: Left & Top)
│
├── [Background-Empty-Star] (Icon/Star-Filled, 20x20px, Color: Neutral/300)
│
└── [Foreground-Clipped-Frame] (Frame, Width: 10px [50%], Height: 20px, Clip content: ON)
└── [Foreground-Active-Star] (Icon/Star-Filled, 20x20px, Color: Amber/500, Absolute 0,0)
具体实操步骤:
- 准备一个标准的单色五角星矢量图标(建议尺寸 20×20px),命名为
Icon/Star-Filled; - 复制一份,底层的五角星填充浅灰色(例如
Neutral/300或#E2E8F0),代表空星背景; - 新建一个 Frame 矩形容器,命名为
Half-Mask,设置尺寸为 宽 10px、高 20px,并勾选属性面板中的 Clip content(裁切内容); - 将第二颗填充了高亮主色(例如琥珀金
Amber/500或#F59E0B)的五角星放入Half-Mask容器内,将其横向与纵向坐标全部归零(X: 0, Y: 0); - 此时由于容器宽度仅为 10px 且开启了裁切,高亮的金色五角星恰好被精准露出左侧的 50%,右侧被无损遮蔽;
- 将底层浅灰全星与该裁切容器组合居中对齐,即获得了一个完美的、无锯齿、无路径畸变且完全与前端 CSS
overflow: hidden/width: 50%原理 1:1 对应的 0.5 半星图元!
扩展:任意百分比精细打分
该容器裁切架构具备极强的工程通用性:若业务需要展示如 4.3 星(4 颗全星 + 0.3 颗半星),只需将第 5 颗星的前景裁切 Frame 宽度从 10px 修改为 20px * 0.3 = 6px 即可,完全不需要重新绘制矢量图元!
10 阶星级矩阵与组件属性(Component Properties)架构 #
在完成原子星元(Star Unit)后,我们将 5 颗星横向编组,构建工业级评分变体集。
1. 原子星元的变体封装 #
先将单个 Star Unit 打包为包含 3 种形态的 Component Set:
Type=Empty(全灰)Type=Half(左半金、右半灰)Type=Full(全金)
同时为每个原子星元添加内边距扩展(Padding),使视觉上 20px 的图元物理占据 32px 或 40px 的外框,确保移动端无障碍触控(Hit Slop)达标。
2. 5 星组合的属性解耦 #
将 5 个原子星元放置在一个水平横向的 Auto Layout 容器 中,间距设为 4px。
为组件配置两大关键 组件属性(Component Properties):
- Variant 属性:
Score(数值变体):- 支持从
0、0.5、1.0、1.5、2.0直至5.0,共 11 种合法分值档位;
- 支持从
- Variant 属性:
State(交互状态):Default(静态常规)Hover(鼠标悬停预览,整体轻微上浮或带有微光滤镜)Focus(获得键盘焦点,外围环绕 2px 的无障碍高亮蓝色轮廓环)Disabled(禁用,整组不透明度 40%)Read-only(只读,消除任何交互反馈)
通过此种架构,任何设计师只需在右侧属性面板下拉选择 Score: 3.5,系统便会自动将前 3 颗星渲染为 Full、第 4 颗渲染为 Half、第 5 颗渲染为 Empty,操作丝滑且绝对规范。
评语文字(Score Tooltip & Label)与 Auto Layout 动态联动 #
高保真评分组件往往需要在右侧或上方提供情绪评价反馈。
[Rating Group Container] (Auto Layout Horizontal, Align: Center, Gap: 12px)
├── [Stars Track] (Auto Layout Horizontal, Gap: 4px)
│ ├── [Star-1: Full]
│ ├── [Star-2: Full]
│ ├── [Star-3: Full]
│ ├── [Star-4: Full]
│ └── [Star-5: Half]
│
├── [Score Number] (Text: "4.5", 14px SemiBold, Tabular Nums)
└── [Sentiment Pill] (Auto Layout Horizontal, Radius: 4px, Padding: 2px 8px)
└── [Label Text] (Text: "非常满意", 12px Medium)
为防止评语文字长短不一导致卡片跳动:
- 建立统一的评语分值映射字典:
0.5 ~ 1.0 星:极差 / 强烈不推荐1.5 ~ 2.0 星:较差 / 低于预期2.5 ~ 3.0 星:一般 / 基本符合3.5 ~ 4.0 星:良好 / 值得推荐4.5 ~ 5.0 星:极佳 / 超出预期
- 将承载评语的文本图层宽度设为 Hug contents,并在外层父级容器中开启居中弹性对齐;
- 将评分数字文本设为启用 OpenType Tabular Figures(等宽数字),确保数字在从
3.0变动到4.5时,右侧的文字位置保持横向对齐平衡。
基于 Figma Variables 的单画框高保真打分状态机 #
在过去,想要在原型中演示“用户点击第 4 颗星,评分变为 4 分且文案变为满意”,必须创建数十个互联画框。如今借助 Figma Variables 变量体系,一个画框即可搞定!
1. 创建核心变量 #
在 Local Variables 面板中创建集合 Rating-Interactive:
current_score(Number): 默认设为0;rating_label(String): 默认设为"请打分";is_hovering(Boolean): 默认设为false;
2. 绑定变量到组件 #
- 将右侧评语文本图层绑定至字符串变量
rating_label; - 将评分组件的变体属性
Score绑定至数值变量current_score;
3. 配置交互触发器(Interaction) #
在原型交互编辑模式下,为 5 颗星分别赋予点击与悬停事件:
- 选中第 1 颗星:添加
On click交互:- Action 1:
Set variable->current_score = 1; - Action 2:
Set variable->rating_label = "极差";
- Action 1:
- 选中第 3 颗星:添加
On click交互:- Action 1:
Set variable->current_score = 3; - Action 2:
Set variable->rating_label = "一般";
- Action 1:
- 选中第 5 颗星:添加
On click交互:- Action 1:
Set variable->current_score = 5; - Action 2:
Set variable->rating_label = "超出预期";
- Action 1:
- 悬停高亮(Hover):添加
While hovering动作,切换为轻微高亮态。
运行原型时,用户随意点击不同星辰,整组星星、分值与评语瞬间同步变幻,整个过程仅存在于一个画框之中,性能极速且逻辑清晰!
Dev Mode 前端工程与无障碍代码交付 #
在 Dev Mode 开发模式 中,我们必须向研发团队清晰输出符合无障碍(a11y)标准与现代 CSS 规范的代码指引。
1. WAI-ARIA 无障碍角色与语义标准 #
评分组件本质上是一组互斥的离散选择,标准无障碍实现应采用 role="radiogroup" 或具备滑动条语义的 role="slider":
<div
class="rating-group"
role="radiogroup"
aria-label="服务质量评分"
>
<div
class="rating-track"
role="slider"
aria-valuenow="4.5"
aria-valuemin="0"
aria-valuemax="5"
aria-valuetext="4.5分,超出预期"
tabindex="0"
>
<!-- 5 颗星矢量或 CSS 遮罩容器 -->
</div>
<span class="rating-label" aria-live="polite">超出预期</span>
</div>
2. 现代 CSS 实现:利用 Mask 遮罩与 CSS 变量实现无缝裁切 #
前端工程师无需拼接多个复杂的 SVG 容器,利用现代 CSS 的 mask-image 配合百分比变量,可以实现像素级无损评分渲染:
.star-rating {
--star-size: 24px;
--star-color-active: #F59E0B;
--star-color-empty: #E2E8F0;
--rating-percent: 90%; /* 4.5 星 = 4.5/5 = 90% */
display: inline-flex;
position: relative;
width: calc(var(--star-size) * 5 + 16px); /* 5颗星加上间距 */
height: var(--star-size);
background: var(--star-color-empty);
/* 5颗星的矢量背景平铺遮罩 */
-webkit-mask: url('/assets/icons/star-shape.svg') repeat-x left center;
mask: url('/assets/icons/star-shape.svg') repeat-x left center;
-webkit-mask-size: var(--star-size) var(--star-size);
mask-size: var(--star-size) var(--star-size);
}
.star-rating::after {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: var(--rating-percent); /* 动态控制填充百分比 */
background: var(--star-color-active);
transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
3. React + Tailwind CSS 生产级组件模板 #
import React, { useState } from 'react';
interface StarRatingProps {
maxStars?: number;
value?: number;
onChange?: (val: number) => void;
readOnly?: boolean;
allowHalf?: boolean;
}
const SENTIMENTS: Record<number, string> = {
1: "极差",
2: "较差",
3: "一般",
4: "满意",
5: "超出预期"
};
export const StarRating: React.FC<StarRatingProps> = ({
maxStars = 5,
value = 0,
onChange,
readOnly = false,
}) => {
const [hoverVal, setHoverVal] = useState<number | null>(null);
const activeValue = hoverVal !== null ? hoverVal : value;
return (
<div className="flex items-center gap-3">
<div
className="flex items-center gap-1 focus:outline-none focus:ring-2 focus:ring-amber-400 rounded-lg p-1"
role="radiogroup"
aria-label="星级评价"
>
{Array.from({ length: maxStars }).map((_, idx) => {
const starNumber = idx + 1;
const isFilled = activeValue >= starNumber;
const isHalf = !isFilled && activeValue >= starNumber - 0.5;
return (
<button
key={idx}
type="button"
disabled={readOnly}
onClick={() => onChange && onChange(starNumber)}
onMouseEnter={() => !readOnly && setHoverVal(starNumber)}
onMouseLeave={() => !readOnly && setHoverVal(null)}
className={`p-1 transition-transform ${readOnly ? 'cursor-default' : 'cursor-pointer hover:scale-110'}`}
aria-label={`${starNumber}星`}
>
<svg className="w-6 h-6" viewBox="0 0 24 24">
<defs>
<linearGradient id={`half-${idx}`}>
<stop offset="50%" stopColor="#F59E0B" />
<stop offset="50%" stopColor="#E2E8F0" />
</linearGradient>
</defs>
<path
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"
fill={isFilled ? "#F59E0B" : isHalf ? `url(#half-${idx})` : "#E2E8F0"}
/>
</svg>
</button>
);
})}
</div>
{activeValue > 0 && (
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-300">
{SENTIMENTS[Math.round(activeValue)] || `${activeValue}星`}
</span>
)}
</div>
);
};
评分组件系统验收与避坑 CheckList #
在正式将 Rate 组件发布到团队基础库并供业务线引用前,请逐项完成自检:
- [ ] 半星裁切无锯齿:0.5 半星是否使用双层容器裁切(Clip Content)或标准分段渐变,杜绝破面与黑线;
- [ ] 44px 无障碍触控热区:单颗星辰的点击区域是否通过 Auto Layout 内边距(Padding)扩展到了移动端合规的 44×44px,避免手指定位困难;
- [ ] 键盘聚焦环(Focus Ring):在键盘 Tab 键切换时,组件是否具备清晰可见的 2px 高对比度焦点指示框;
- [ ] Tabular Nums 等宽排印:分值数字图层是否开启了等宽数字,杜绝打分数值在变动时引发后方文案抖动;
- [ ] Dev Mode 语义化交付:是否已在 Dev Mode 标注区 提供了完整的 WAI-ARIA 属性与 CSS 遮罩代码参考。
核心手册与速查工具
高频手册与实用设计生产力工具直达常见问题与答疑 (FAQ)
高频疑难解析与避坑指南完全不需要。绝大多数核心功能在免费 Starter 计划中均可完整体验与实操练习,无需额外付费购买付费团队版。