前端圈里星级评分组件都快被玩烂了,大多时候得靠JS来联动值和样式。但有没有想过,光用CSS就能搞定一个能点能选的评分条?其实一个
<input type="range">元素就藏着这个骚操作,配上几行样式代码,直接变身五颗金星,连半星评分都不在话下。下面就把这套纯CSS方案拆开揉碎,从原理到实操一步步盘清楚。
核心材料
<input type="range">这个滑动条天生就能选数值,min和max框住了边界。把它变成星星评分,本质上就是给滑动条换皮:把滑轨遮成五颗星,再把滑块伪装成染色刷。滑块滑到第几颗星,就把前面所有星星刷成金色。
整个改造只用CSS,不碰JS,HTML里孤零零一个元素就够:
<input type="range" min="1" max="5">接下来所有魔法都发生在样式表里。
主元素塑形
滑动条本体要先长成星星底板的模样。宽高比得跟星星数量挂钩,五颗星就用aspect-ratio:5,这样宽度是高度的五倍。高度用自定义属性--s控制,方便以后调大小。
input[type="range"] {
--s: 100px; /* 每颗星的尺寸 */
height: var(--s);
aspect-ratio: 5;
appearance: none; /* 扒掉浏览器默认皮肤 */
}appearance: none这一步很关键,不同浏览器下原生滑动条的样式五花八门,去掉之后才能从头画。但有个副作用——键盘聚焦时的外轮廓也没了,后面再想办法补回来。
星星的形状怎么来?用mask属性。可以塞一个SVG做遮罩,也可以用渐变拼出来。渐变方案里,用repeating-linear-gradient或mask-image配合mask-size: var(--s),让星星图案水平重复五次。SVG方案代码更短,但渐变方案在某些场景更灵活。这里给出渐变写法:
input[type="range"] {
mask-image: repeating-linear-gradient(90deg, #000 0 calc(100% / 5), transparent 0);
mask-size: var(--s);
}实际上真正做星星形状得用更精细的径向渐变或mask-composite,原文给出的demo里用了更复杂的遮罩组合,但核心就是让滑轨区域露出星星轮廓。
滑块变身刷子
滑块是那个可以拖拽的小圆钮,现在要把它变成一把刷子——滑到哪,就把哪边的星星涂成金色。先让滑块瘦成一条1px宽的透明线,这样不会挡住星星图案。
input[type="range"]::-webkit-slider-thumb {
width: 1px;
height: var(--s);
appearance: none;
}不同浏览器对伪元素的支持不一样,Chrome、Edge认::-webkit-slider-thumb,Firefox认::-moz-range-thumb。得把样式复制两份,不能写在一起,否则整个选择器会废掉。
滑块变刷子的核心武器是border-image。用线性渐变做颜料,然后通过border-image的outset扩展让颜色溢出到滑块两侧。比如想刷满五颗星,就设置一个超大的横向扩展值(如500px),渐变里的颜色分界点设置在50% + var(--s)/2,保证第一块颜色覆盖到当前选中位置左边的所有区域。
input[type="range"]::-webkit-slider-thumb {
width: 1px;
border-image: conic-gradient(at calc(50% + var(--s) / 2), gold 50%, grey 0) fill 0 // var(--s) 500px;
appearance: none;
}这段代码里conic-gradient在指定锚点处切出两种颜色,fill 0 // var(--s) 500px表示上下不扩展,左右各扩展500px(远大于组件宽度)。滑块移到第3颗星位置时,渐变的颜色交界线正好卡在第3颗星边缘,左边金色右边灰色,看起来就是前面三颗星全亮。
位置校准让星星对齐
默认情况下滑块的移动范围是从滑轨最左到最右,会导致滑块跑到星星边缘而不是中心。为了让滑块始终对准每颗星的中心,需要在主元素左右加内边距,大小为半颗星宽calc(var(--s) / 2),同时把box-sizing设为border-box,这样内边距不会撑大总宽。
input[type="range"] {
padding-inline: calc(var(--s) / 2);
box-sizing: border-box;
}这样一来滑块的活动范围被压缩到中间区域,每次点击星星时滑块正好落在该星星正中央。
半星精度扩展
很多场景需要半星评分,比如3.5分。把step属性改成0.5,min也改成0.5,就能让滑块每次移动半格。CSS这边对应调整内边距和渐变偏移量,从半颗星宽改成四分之一星宽。
<input type="range" min="0.5" step="0.5" max="5">input[type="range"] {
--_s: calc(0.5 * var(--s) / 2);
padding-inline: var(--_s);
}
input[type="range"]::-webkit-slider-thumb {
border-image: conic-gradient(at calc(50% + var(--_s)), gold 50%, grey 0) fill 0 // var(--s) 500px;
}用attr()函数甚至可以动态读取step和max属性,写一个通用模板,改HTML属性就能切换整星或半星模式。不过attr()在非Chrome浏览器里支持还不太稳,得留好回退样式。
键盘操作别丢焦点
去掉原生样式后,键盘Tab聚焦到滑动条上看不到外轮廓,视障用户就懵了。解决办法有两种:套一个外层<span>,用:has(:focus-visible)给外层加outline;或者在遮罩上做手脚,保留一个小区域不遮住,让浏览器默认轮廓能露出来。第二种方法更干净,不增加DOM元素,但遮罩配置会复杂些。
/* 外层包裹方案 */
span:has(:focus-visible) {
outline: 2px solid blue;
}换形状玩出更多花样
这套方案不只做星星,换一下mask-image的素材,立马变身爱心评分、蝴蝶评分、信号强度评分。比如用爱心SVG:
input[type="range"] {
mask-image: url('heart.svg');
mask-size: var(--s);
}甚至不需要矢量图,一张PNG透明底图也能当遮罩。音量控制那种阶梯条也能做,用重复的矩形渐变当遮罩就行。
| 形状类型 | 遮罩方式 | 适用场景 |
|---|---|---|
| 星星 | 渐变或SVG | 常见评分 |
| 爱心 | SVG或PNG | 点赞收藏 |
| 信号格 | 多层渐变 | 音量信号 |
动手试一下,改改mask-image和border-image里的颜色值,一套代码变出几十种组件。这波操作下来,纯CSS也能把交互组件玩得飞起,以后遇到评分条直接一个input搞定,省掉一堆JS监听。
