BEM 无法解决半星渲染问题,因其本质是坐标计算而非样式开关;应将 BEM 用于结构分层(如 .rating、.rating__stars),而非硬套半星状态,JS 才负责动态填充与触摸适配。

纯 CSS 无法可靠表达半星状态,BEM 只能命名“意图”,不能解决渲染本质问题。 所有尝试用 .star--half 或 .star__fill--35 这类 BEM 类名去控制视觉上“切半颗星”的方案,最终都会撞上浏览器不支持小数级像素裁剪、::before 伪元素无法局部覆盖、mask 在 iOS Safari 上失效或 touch 不响应这些硬限制。
为什么 .star--half 在 BEM 里是误导性命名
BEM 的 --modifier 本意是描述组件的**离散状态**(如 .button--disabled),但半星不是离散态——它是连续值(3.2 / 3.7 / 4.1)映射到像素坐标的中间结果。用 .star--half 暗示“这颗星被切成两半”,实际却要靠 JS 动态计算 background-position 或 clip-path 的数值,语义和实现严重脱节。
- 用户看到
.star--half,会默认它是个静态开关,但真实场景中同一颗星在不同评分下可能显示 0% / 30% / 70% 填充,根本不是“半” - 所有基于
background-image+background-size的“半星”方案,都依赖预设图或渐变,.star--half无法表达填充比例差异 - 当后端返回
"rating": 3.7,你不能写.star--37—— BEM 不支持动态数字修饰符,强行加会破坏可维护性
真正该用 BEM 命名的,是“容器+交互层+数据层”三部分
把 BEM 用在结构分层上,比硬套在“半星”上更有效。比如:
-
.rating:整个评分容器,负责布局与事件委托 -
.rating__stars:五颗星的视觉区域,设position: relative,供 JS 获取getBoundingClientRect() -
.rating__input:隐藏的<input type="radio">,用clip隐藏而非display: none -
.rating__label:可见的星星载体,绑定for属性,接受点击并触发对应 input
这样命名后,JS 可以干净地操作 .rating__stars 下的坐标,再把计算出的值(如 3.7)映射到 .rating__input[value="37"] 或存为字符串,BEM 不越界管渲染逻辑。
立即学习“前端免费学习笔记(深入)”;
移动端必须放弃 :hover + .star--half 组合
iOS Safari 根本不信任 :hover,哪怕你写了 .star--half:hover,真机上点一次就失效。更糟的是,touchstart 触发后若没立刻取 touches[0].clientX,滚动或快速滑动会导致坐标错位。
- 别给
.star加ontouchstart=""就以为能激活伪类——这只是让:checked生效的前提,不是半星的解法 - 移动端应直接降级为整星选择:
.rating__label点击即触发对应.rating__input,不尝试悬停定位 - 如果产品强求半星,JS 必须监听
touchmove并调用event.preventDefault(),否则 iOS 会双击缩放
半星的本质是坐标计算,不是样式开关。BEM 能帮你理清 DOM 职责边界,但别指望它替你算 clientX - rect.left < starWidth / 2 —— 那块逻辑藏在 JS 里,且永远需要 fallback 到整星。


















