本文介绍如何利用现代 CSS 的 :has() 伪类,结合隐藏单选按钮与伪元素,将传统 radio 按钮优雅替换为可交互的彩色星标,并精准控制已选星标及后续星标的颜色状态。
本文介绍如何利用现代 CSS 的 :has() 伪类,结合隐藏单选按钮与伪元素,将传统 radio 按钮优雅替换为可交互的彩色星标,并精准控制已选星标及后续星标的颜色状态。
要实现“点击星标后变色”的效果,关键在于脱离对 :checked 直接父级关系的错误依赖,并采用语义清晰、逻辑可控的 CSS 选择器策略。原代码中 .uk-margin > label > div > input:checked, label 这一选择器存在两个根本问题:一是逗号分隔导致 label 被无条件全局染色;二是无法跨标签关联——input:checked 与后续 label 并非父子或相邻关系,纯 CSS 传统方式难以向上/向右影响兄弟元素。
✅ 正确解法是使用 CSS :has() 伪类(现代浏览器支持,Chrome 105+、Firefox 117+、Safari 15.4+),它允许基于子元素状态反向影响祖先容器,再通过后代选择器精确着色:
/* 隐藏原生 radio,保留可访问性 */.uk-margin > label > div > input {position: absolute;top: -9999px;opacity: 0; /* 更稳妥的隐藏方式,避免 focus 丢失 */}/* 星标基础样式 */.uk-margin > label {width: 1em;white-space: nowrap;cursor: pointer;font-size: 30px;color: #ccc;}.uk-margin > label::before {content: '★ ';}/* 悬停时:当前及右侧所有星标高亮为金色 */.uk-margin > label:hover,.uk-margin > label:hover ~ label {color: #deb217;}/* 悬停时增强:确保伪元素颜色同步(部分浏览器需显式声明) */.uk-margin:hover > label::before {color: #deb217;}.uk-margin:hover > label:hover ~ label::before {color: #c59b08;}/* 关键:当选中某 radio 时,整个 .uk-margin 容器满足 :has(:checked) 条件 */.uk-margin:has(:checked) > label::before {color: #ff0000; /* 所有星标统一变红(未选中态回退由 hover 覆盖) */}/* 进阶:仅让「已选星标及其右侧星标」变为深金色(模拟半星/评分渐变) */.uk-margin:has(:checked) > label:has(:checked) ~ label::before {color: #c59b08;}
? 重要说明与注意事项:
:has() 是父容器级别的状态判断,因此必须作用于共同父级(即 .uk-margin),而非单个 label;display: flex 替代 float(如示例中所示),确保布局稳定且无需额外清除浮动;opacity: 0 比 visibility: hidden 或 display: none 更优——它保留表单可访问性(屏幕阅读器仍可读取)、键盘 Tab 导航正常、且不影响 :checked 状态触发;change 事件并动态添加 class,但本文聚焦纯 CSS 现代实践;:has()(可通过 caniuse.com/css-has 查询)。最终效果:用户点击任意星标后,该星标及右侧所有星标变为深金色(#c59b08),而左侧星标保持灰色;悬停时则实时预览评分效果,交互直观、语义清晰、无需 JS 干预。