radix-vue Rating 评分组件完全指南:分数值、悬停预览与 Radio Group 无障碍实现
【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue
导读
本文以 radix-vue(现以reka-ui包名发布)仓库中的 Rating 组件文档为核心,系统讲解星级评分输入组件的完整用法:从组件解剖结构(Anatomy)、API 参数表,到分数值(半星/四分之一星)的 CSS 变量实现原理、可清除、悬停预览、自定义长度与只读禁用等实战场景,并深入对应源码与测试用例,帮助你掌握一个"开箱即用、键盘可操作、支持表单提交"的评分组件的全部技术细节。读完本文,你将能够独立在 Vue 3 项目中实现从整星到任意分数粒度、兼顾 RTL 与无障碍的评分交互。
Rating 是什么
Rating 是一个星级评分输入组件,用户通过它选择一个分数值,并支持小数(分数)取值。它由RatingRoot、RatingItem、RatingItemIndicator三个部件组成,底层构建在 Radio Group 之上,因此天然继承了一整套单选组的无障碍能力:表单提交、焦点管理、方向键导航全部开箱即用。
其核心特性(见 rating.md 文档)包括:
- 支持受控与非受控两种模式(
modelValue/defaultValue); - 通过
step属性支持分数评级(半星、四分之一星等); - 悬停时预览指针下的值;
- 点击当前激活值可清除评分;
- 基于 Radio Group 构建,具备完整的键盘导航与表单支持;
- 支持 RTL(从右到左)方向;
- 暴露 CSS 变量,用于渲染部分步骤(partial steps)。
安装
从命令行安装组件(该组件以reka-ui包名发布,即 radix-vue 项目的当前发布名):
npm install reka-ui安装完成后即可在组件中按需导入 Rating 的三个部件。
组件解剖:Anatomy
评分组件由三个部件拼装而成:RatingRoot通过默认插槽暴露items(每一项的列表),每个RatingItem再根据根组件的step属性计算出自身包含的steps并暴露出来,因此你需要为每一个 step 渲染一个 indicator。最小结构如下:
<script setup> import { RatingItem, RatingItemIndicator, RatingRoot } from 'reka-ui' </script> <template> <RatingRoot v-slot="{ items }"> <RatingItem v-for="item in items" :key="item" v-slot="{ steps }" :item="item" > <RatingItemIndicator v-for="step in steps" :key="step" :step="step" /> </RatingItem> </RatingRoot> </template>各部件职责一览:
- RatingRoot:包含评分全部状态(当前值、悬停值、items 列表),内部渲染一个 Radio Group;
- RatingItem:包裹单个评分值(如一颗星),根据根部的
step计算组成该 item 的steps,默认渲染为<label>; - RatingItemIndicator:为 item 的每个 step 渲染交互指示器,根据当前(或悬停)值反映该 step 是否激活。
API 参考
Root(RatingRoot)
包含评分的所有部件并提供评分状态,底层渲染为 Radio Group,因此支持表单提交与键盘导航。完整参数见 RatingRoot 元数据:
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
as | 渲染为的元素或组件,可被asChild覆盖 | AsTag \| Component | "div" |
asChild | 将默认渲染元素改为传入的子元素,并合并其 props 与行为 | boolean | - |
clearable | 为true时,点击当前选中值将评分重置为0 | boolean | - |
defaultValue | 初始渲染时的评分值,非受控模式下使用 | number | - |
dir | 阅读方向,省略时继承ConfigProvider或默认为 LTR | "ltr" \| "rtl" | - |
disabled | 为true时阻止用户与 radio 项交互 | boolean | - |
hoverable | 为true时,悬停时预览指针下的值 | boolean | - |
length | 渲染的评分项数量 | number | 5 |
loop | 为true时键盘导航在首尾之间循环 | boolean | - |
modelValue | 受控评分值,可用v-model绑定 | number | - |
name | 表单字段名,随表单以 name/value 形式提交 | string | - |
orientation | 组件方向 | "vertical" \| "horizontal" | "horizontal" |
required | 为true时表示提交表单前必须设置值 | boolean | - |
step | 每个评分项被划分的粒度 | 1 \| 0.5 \| 0.25 \| 0.1 | 1 |
事件:update:modelValue,载荷为[payload: number],在值变化时触发。
插槽:默认插槽暴露modelValue(number \| undefined)与items(number[])。
Root 还暴露以下数据属性:
| 属性 | 值 |
|---|---|
[data-disabled] | 禁用时出现 |
[data-orientation] | vertical/horizontal |
Item(RatingItem)
包裹单个评分值(如一颗星)。它根据根部的step属性计算组成该 item 的steps列表,并通过默认插槽暴露,默认渲染为<label>。完整参数见 RatingItem 元数据:
| 名称 | 说明 | 类型 | 必填 |
|---|---|---|---|
as | 渲染为的元素或组件 | AsTag \| Component | 否(默认"label") |
asChild | 改为渲染传入的子元素 | boolean | 否 |
item | 该 item 在评分中的 1-based 索引(如第 3 颗星) | number | 是 |
插槽:默认插槽暴露steps(number[])。
ItemIndicator(RatingItemIndicator)
为 item 的每个 step 渲染交互指示器,根据当前(或悬停)值反映该 step 是否激活。完整参数见 RatingItemIndicator 元数据:
| 名称 | 说明 | 类型 | 必填 |
|---|---|---|---|
as | 渲染为的元素或组件 | AsTag \| Component | 否(默认"div") |
asChild | 改为渲染传入的子元素 | boolean | 否 |
step | 该指示器表示的 step 值 | number | 是 |
指示器暴露的数据属性:
| 属性 | 值 |
|---|---|
[data-state] | active(激活时) |
[data-disabled] | 禁用时出现 |
渲染部分步骤的 CSS 变量
当step小于1时,需要用 CSS 变量裁剪并堆叠各个 step 的宽度。RatingItemIndicator暴露以下三个 CSS 变量(表格来源:rating.md 文档):
| CSS 变量 | 说明 |
|---|---|
--reka-rating-item-step-width | 该 step 在 item 内应占据的宽度,如半星为50% |
--reka-rating-item-step-opacity | step 可见时为1,否则为0(用于堆叠重叠的 step) |
--reka-rating-item-step-z-index | step 的堆叠顺序,使较小的 step 渲染在较大的之上 |
一个典型的分数指示器会裁剪自身宽度并用这些变量堆叠步骤:
<RatingItemIndicator :step="step" class="absolute overflow-hidden w-[var(--reka-rating-item-step-width)] opacity-[var(--reka-rating-item-step-opacity)] z-[var(--reka-rating-item-step-z-index)]" />实战示例
1. 分数评级(Fractional rating)
通过step属性允许小于1的值。每个RatingItem会被拆分为多个steps,每个 step 渲染自己的指示器,并用暴露的 CSS 变量裁剪宽度。例如设置step="0.5"即可获得半星评分:
<script setup> import { RatingItem, RatingItemIndicator, RatingRoot } from 'reka-ui' import { ref } from 'vue' const rating = ref(2.5) </script> <template> <RatingRoot v-slot="{ items }" v-model="rating" :step="0.5" > <RatingItem v-for="item in items" :key="item" v-slot="{ steps }" :item="item" class="relative" > <RatingItemIndicator v-for="step in steps" :key="step" :step="step" class="absolute overflow-hidden w-[var(--reka-rating-item-step-width)] opacity-[var(--reka-rating-item-step-opacity)] z-[var(--reka-rating-item-step-z-index)]" /> </RatingItem> </RatingRoot> </template>step的合法取值为1 | 0.5 | 0.25 | 0.1,对应整星、半星、四分之一星与十分之一星。
2. 可清除(Clearable)
使用clearable属性让用户再次点击当前选中值即可将评分重置为0:
<template> <RatingRoot v-model="rating" clearable > <!-- ... --> </RatingRoot> </template>3. 悬停预览(Hover preview)
使用hoverable属性在提交前预览指针下的值。悬停时,RatingItemIndicator会对悬停值及以下的所有 step 暴露data-state="active":
<template> <RatingRoot v-model="rating" hoverable > <!-- ... --> </RatingRoot> </template>4. 自定义长度(Custom length)
用length属性改变渲染的评分项数量,默认值为5:
<template> <RatingRoot v-model="rating" :length="10" > <!-- ... --> </RatingRoot> </template>5. 只读 / 禁用(Read-only / disabled)
使用disabled属性阻止交互,例如展示平均评分等只读场景:
<template> <RatingRoot :default-value="4" disabled > <!-- ... --> </RatingRoot> </template>源码级原理:三个部件如何协作
深入 packages/core/src/Rating 目录,可以看清上述 API 的底层实现。
状态与 items 生成(RatingRoot.vue)
RatingRoot.vue 用useVModel管理受控/非受控值,默认值参数为orientation: 'horizontal'、length: 5、step: 1。items是一个计算属性,由length生成1..length的整数数组:
const items = computed(() => { return Array.from({ length: length.value }, (_, i) => i + 1) })changeModelValue中实现了clearable逻辑:当clearable为真且点击值等于当前值时,将值重置为0;changeHoveredRating则在disabled或非hoverable时直接忽略。Root 通过provideRatingRootContext向子孙注入modelValue、items、hoveredRating、disabled、step及两个变更函数。
值得注意的是模板层:Root 渲染RadioGroupRoot,并在@mouseleave时无条件调用resetHoveredRating()清空悬停预览——注释说明这是为了确保即使悬停中途切换了hoverable/disabled状态,指针离开后残留的预览也能被清除。
steps 的计算(RatingItem.vue)
RatingItem.vue 根据根部step计算本 item 包含的 steps:以item - 1为起点、item为终点,按stepSize切分,向上取整得到 step 数量,并生成0.5 → 1 → 1.5 → 2 …这样的递增序列(保留两位小数以避免浮点误差):
const steps = computed(() => { const groupStartValue = (props.item - 1) const groupEndValue = props.item const stepSize = rootContext.step.value const numberOfSteps = Math.ceil((groupEndValue - groupStartValue) / stepSize) return Array.from({ length: numberOfSteps }, (_, index) => Number((groupStartValue + (index + 1) * stepSize).toFixed(2))) })Item 本身只是渲染Primitive(默认label)并把steps通过插槽交给上层。
激活态与 CSS 变量(RatingItemIndicator.vue)
RatingItemIndicator.vue 是表现层的核心:
isActive决定data-state="active":悬停值大于0时取step <= hoveredRating,否则取step <= modelValue,即悬停预览优先于已提交值;isVisible决定--reka-rating-item-step-opacity是否为1:当键盘聚焦到该元素、step为整数(step % 1 === 0)、或该 step 恰好等于当前悬停值/模型值时可见——这正是"堆叠的半星只露出高亮部分"的实现细节;- 三个 CSS 变量在
style绑定中直接计算:--reka-rating-item-step-width为step % 1 || 1乘以 100% 的宽度(如 2.5 的 step 显示 50%);--reka-rating-item-step-z-index为该 step 在 steps 列表中的倒序位置,保证小数部分叠在大星之上。
每个指示器实际渲染为一个RadioGroupItem(值为该 step),@select时调用changeModelValue(step),@mouseenter时调用changeHoveredRating(step);内部再用RadioGroupIndicator承载实际图标(如星形 SVG),这也解释了为什么整套组件天然具备 radio 的语义与表单能力。
行为验证:测试用例
仓库自带的测试 Rating.test.ts 验证了上述全部核心行为:
- 默认选中:
defaultValue: 1, length: 3时,第一个 radio 的data-state为active,其余为undefined; - 键盘导航:聚焦第一项后按下
ArrowDown,会触发update:modelValue且载荷为2,第二项变为active并获得焦点;再按ArrowUp回到第一项; - 悬停预览:
hoverable时对第三项触发mouseEnter,第一、二项均变为active;mouseLeave根节点后预览重置回模型值; - 禁用状态:
disabled时点击任意项均不改变状态,所有 radio 都带disabled与data-disabled属性; - 无障碍:默认与禁用场景均通过
axe自动化无障碍测试(toHaveNoViolations)。
无障碍与键盘交互
Rating 构建在 Radio Group 原语之上,遵循 WAI-ARIA 的 Radio Group 设计模式(详见文档 Accessibility 一节)。建议为每个 step 的指示器通过aria-label提供无障碍标签,让屏幕阅读器用户理解每个指示器代表的分值。
支持的键盘交互(表格来源:rating.md 文档):
| 按键 | 行为 |
|---|---|
Tab | 将焦点移动到已选项或评分第一项 |
Space | 焦点在未选项上时,选中该值 |
ArrowDown | 将焦点和选中移到下一项 |
ArrowRight | 将焦点和选中移到下一项 |
ArrowUp | 将焦点和选中移到上一项 |
ArrowLeft | 将焦点和选中移到上一项 |
配合dir/ 全局ConfigProvider可支持 RTL 方向;在 RTL 下左右方向键的语义会相应反转。若启用loop,键盘导航会在首尾之间循环。
小结
Rating 是 radix-vue(reka-ui)中"组合式原语"的典型代表:三部件分工明确——RatingRoot管状态与方向,RatingItem按step切分粒度,RatingItemIndicator用 CSS 变量完成部分步骤的裁剪与堆叠;同时底层复用 Radio Group 换来了表单提交、焦点管理与完整键盘导航。若需进一步定制,可参考仓库中的 story 示例 RatingDefault.story.vue(其中展示了hoverable+step="0.5"+ 图标组合的真实用法),以及组件导出入口 index.ts。
【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考