纯HTML+CSS星级评分需用radio单选+label包裹+伪元素绘星,隐藏input用opacity:0,CSS用~选择器和:checked实现点击反馈,语义上高分在右需flex-reverse或RTL控制,仅支持整星单向选择。

用纯 HTML + CSS 实现可点击的星级评分(无 JS)
纯 HTML 能做星级评分,但仅限于「单向选择 + 表单提交」场景,核心靠 <input type="radio"> 和相邻兄弟选择器 ~ 配合 :checked 状态实现视觉反馈。不能实时读取值、不能半星、不能取消选中——这些必须加 JS。
常见错误是直接用 <div> 堆星星再绑 click,结果表单里没实际值,后端收不到数据。
<ul>
<li>每颗星必须包裹在 <code><label></label> 里,且 <input> 在 label 内或通过 for 关联
name 必须相同,否则无法互斥<span></span>)要放在对应 <input> 后面,才能用 input:checked ~ span 触发样式checked 属性,不是 defaultChecked(那是 React 的)<fieldset> <legend>评分</legend> <input type="radio" id="s5" name="rating" value="5"> <label for="s5"></label> <input type="radio" id="s4" name="rating" value="4"> <label for="s4"></label> <input type="radio" id="s3" name="rating" value="3"> <label for="s3"></label> <input type="radio" id="s2" name="rating" value="2"> <label for="s2"></label> <input type="radio" id="s1" name="rating" value="1"> <label for="s1"></label> </fieldset>
CSS 星级渲染的关键:隐藏 input + 用伪元素画星
直接对 <input> 设置颜色没用,必须隐藏它,再用 label::before 或 label::after 绘制 ★ 符号。否则用户看到的是原生 radio 按钮,不是星星。
容易踩的坑是忘记设置 label 的 cursor: pointer,导致看起来不可点;或者用 display: none 隐藏 input 后,:checked ~ label 失效(因为 DOM 顺序断了),正确做法是用 position: absolute; opacity: 0; width: 0; height: 0;。
立即学习“前端免费学习笔记(深入)”;
- 五星按从左到右排列,但语义上 5 分应最右,所以 CSS 里用
flex-direction: row-reverse或 float 反转顺序 - 用
unicode-bidi: bidi-override和direction: rtl控制 ★ 渲染方向,避免文字流干扰 - 悬停时高亮“当前星及左侧所有星”,靠
label:hover ~ label, label:hover实现,但注意这会覆盖已选中的状态,需用input:checked ~ label优先级更高来兜底
用 JavaScript 补全半星、实时反馈和取消功能
原生 radio 不支持半星和取消,click 事件监听必须绑定在 label 或整个容器上,不能只绑 input——因为 input 被隐藏了,用户点不到。
关键逻辑是:记录鼠标按下时的星数,拖动时动态更新,松开后固化。取消功能本质是判断是否再次点击同一颗星,需要比对 event.target 和当前选中值。
- 半星用两个重叠的
<span></span>:一个满星,一个遮罩层,用clip-path或overflow: hidden+transform: translateX()截取宽度 - 不要用
mousemove实时计算,性能差;改用mouseover+ 节流,只在进入新星时更新预览 - 移动端要额外监听
touchstart/touchmove/touchend,且防止click冒泡触发两次 - 获取当前值别用
document.querySelector('input[name="rating"]:checked'),而应缓存到组件 state 或 data 属性里,避免重复查询
服务端接收和验证 rating 字段的注意事项
HTML 表单提交时,rating 是字符串,比如 "3",不是数字。后端收到后必须校验范围(1–5)、类型(整数)、非空,不能直接转 int 后运算。
常见漏洞是前端限制了 1–5,但攻击者能绕过 HTML 直接 POST rating=999 或 rating=abc,后端不校验就可能崩掉数据库或暴露逻辑。
- PHP 中用
filter_var($rating, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 5]]) - Node.js Express 中用
parseInt(req.body.rating, 10)后立刻判断isNaN()和边界 - 别依赖
required属性——它只在浏览器前端生效,禁用 JS 后无效 - 如果允许未评分(即“暂不评价”),必须显式提供一个
value=""的 radio 或单独的“跳过”按钮,不能留空
半星值(如 3.5)必须由 JS 拼成字符串传给隐藏 input,后端再解析小数,但要注意浮点精度和四舍五入策略——多数业务其实只需要整数分,强行半星反而增加存储和统计复杂度。



















