news 2026/9/17 3:54:11

radix-vue Rating 评分组件完全指南:分数值、悬停预览与 Radio Group 无障碍实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
radix-vue Rating 评分组件完全指南:分数值、悬停预览与 Radio Group 无障碍实现

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 是一个星级评分输入组件,用户通过它选择一个分数值,并支持小数(分数)取值。它由RatingRootRatingItemRatingItemIndicator三个部件组成,底层构建在 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-
clearabletrue时,点击当前选中值将评分重置为0boolean-
defaultValue初始渲染时的评分值,非受控模式下使用number-
dir阅读方向,省略时继承ConfigProvider或默认为 LTR"ltr" \| "rtl"-
disabledtrue时阻止用户与 radio 项交互boolean-
hoverabletrue时,悬停时预览指针下的值boolean-
length渲染的评分项数量number5
looptrue时键盘导航在首尾之间循环boolean-
modelValue受控评分值,可用v-model绑定number-
name表单字段名,随表单以 name/value 形式提交string-
orientation组件方向"vertical" \| "horizontal""horizontal"
requiredtrue时表示提交表单前必须设置值boolean-
step每个评分项被划分的粒度1 \| 0.5 \| 0.25 \| 0.11

事件update:modelValue,载荷为[payload: number],在值变化时触发。

插槽:默认插槽暴露modelValuenumber \| undefined)与itemsnumber[])。

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

插槽:默认插槽暴露stepsnumber[])。

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-opacitystep 可见时为1,否则为0(用于堆叠重叠的 step)
--reka-rating-item-step-z-indexstep 的堆叠顺序,使较小的 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: 5step: 1items是一个计算属性,由length生成1..length的整数数组:

const items = computed(() => { return Array.from({ length: length.value }, (_, i) => i + 1) })

changeModelValue中实现了clearable逻辑:当clearable为真且点击值等于当前值时,将值重置为0changeHoveredRating则在disabled或非hoverable时直接忽略。Root 通过provideRatingRootContext向子孙注入modelValueitemshoveredRatingdisabledstep及两个变更函数。

值得注意的是模板层: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-widthstep % 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-stateactive,其余为undefined
  • 键盘导航:聚焦第一项后按下ArrowDown,会触发update:modelValue且载荷为2,第二项变为active并获得焦点;再按ArrowUp回到第一项;
  • 悬停预览hoverable时对第三项触发mouseEnter,第一、二项均变为activemouseLeave根节点后预览重置回模型值;
  • 禁用状态disabled时点击任意项均不改变状态,所有 radio 都带disableddata-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管状态与方向,RatingItemstep切分粒度,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),仅供参考

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

吃透链表三板斧:逆序、判环、合并,搞定算法面试

1. 链表题难在哪&#xff1a;看着简单&#xff0c;一写就崩如果你去翻各大平台的算法题库&#xff0c;链表题永远是绕不过去的一块。数组题还能靠直觉蒙一蒙&#xff0c;链表题一旦指针指错&#xff0c;整段逻辑全部崩盘。我见过不少刷了几百道题的人&#xff0c;回头写一个单链…

作者头像 李华
网站建设 2026/9/17 3:53:20

基于BiRNN的锂电池RUL预测:MATLAB实现与GUI设计

做电池管理系统的人应该都遇到过这样的场景&#xff1a;同一批电芯&#xff0c;有些能稳定跑500多次循环&#xff0c;有些200多次就开始容量跳水。如果能在电芯真正失效之前给出一个相对准确的剩余寿命预判&#xff0c;运维策略和系统安全性都会完全不一样。这个项目就是围绕这…

作者头像 李华
网站建设 2026/9/17 3:52:54

深度学习结合LSTM的车内主动噪声控制Matlab实现与效果分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华