换一种方式来实现CSS评分组件

简介: 换一种方式来实现CSS评分组件

评分组件想必大家都碰到过了吧,纯 CSS 方式网上也有很多实现,一般都是通过 input[type="radio"] 实现,比如这个点击预览


大致原理如下


  1. 通过 flex-direction: row-reverse 或者其他手段(dirction:rtltransform:scaleX(-1)),将元素位置翻转
  2. 配合 :checked~ 选中视觉上的前面兄弟节点


实现非常精妙,无需 js 接入,兼容性也不错,不过这里还有两个瑕疵


  1. 视觉展示和页面结构不一致,比如给每个 input 添加 value 属性,需要倒序来加,不符合一般人的认知
<div class="raido-group">
    <input type="radio" name="rate" value="5">
    <input type="radio" name="rate" value="4">
    <input type="radio" name="rate" value="3">
    <input type="radio" name="rate" value="2">
    <input type="radio" name="rate" value="1">
</div>


  1. 当组件聚焦后,通过键盘左右键切换也是相反的,具体表现就是,按方向键 “右”,评分却减少,反之亦然

image.png

优化


上面提到的两个瑕疵其实都是元素位置翻转引起的,目的也是为了实现视觉上的前置兄弟节点选择器,有没有什么办法规避这个问题呢?答案就是重置,处理如下


  1. 默认情况下都是选中的样式
  2. 配合 :checked~ 选中后面的元素,样式设置为未选中的样式

具体实现如下,html 为正常结构

<div class="raido-group">
    <input type="radio" name="rate" value="1">
    <input type="radio" name="rate" value="2">
    <input type="radio" name="rate" value="3">
    <input type="radio" name="rate" value="4">
    <input type="radio" name="rate" value="5">
</div>


下面是关键样式

[type="radio"]{
    -webkit-appearance: none;
    width: 20px;
    height: 20px;
    -webkit-mask: url("data:image/svg+xml,%3Csvg width='12' height='11' viewBox='0 0 12 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 0l1.693 3.67 4.013.476L8.74 6.89l.788 3.964L6 8.88l-3.527 1.974.788-3.964L.294 4.146l4.013-.476L6 0z' fill='%23F67600'/%3E%3C/svg%3E");
    -webkit-mask-size: 20px;
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-position: center;
    background-color: coral; /*默认是选中的样式,橙色*/
    margin: 0;
    transition: .2s;
}
[type="radio"]:checked~[type="radio"]{
    background-color: #E8EAED; /*未选中的样式,灰色*/
}


这里的星星图形是通过 mask 实现的,根据实际需求也可采用传统背景图片的方式


可以看到优化后的方案代码更加精简了,html 也符合常规认知,键盘切换也正常了


image.png

当然这个实现也还是有点瑕疵的,由于默认是选中状态,所以即使没有任何


:checked, 也会是全部高亮(满分)的情况,不会有0分的情况,所以推荐默认把最后一项设置 checked 属性。当然实际场景也并不会有0分的选项,很多场景下都是默认满分,所以这个小问题无伤大雅~


半颗星的实现


在上面的基础上,半颗星也很容易,只需要使用 10 个 input,每个只占据一半的空间即可


  1. 10个input
  2. 每个input宽度为之前的一半
  3. 配合 nth-child(odd)nth-child(even)设置显示区域

具体实现如下

<div class="raido-group rate-half">
    <input type="radio" name="rate-half" value="0.5">
    <input type="radio" name="rate-half" value="1">
    <input type="radio" name="rate-half" value="1.5">
    <input type="radio" name="rate-half" value="2">
    <input type="radio" name="rate-half" value="2.5">
    <input type="radio" name="rate-half" value="3">
    <input type="radio" name="rate-half" value="3.5">
    <input type="radio" name="rate-half" value="4">
    <input type="radio" name="rate-half" value="4.5">
    <input type="radio" name="rate-half" value="5">
</div>


下面是关键样式

.rate-half [type="radio"]{
    width: 10px; /* 宽度设置一半 */
}
.rate-half [type="radio"]:nth-child(odd){
    -webkit-mask-position: left; /* 设置星星的显示区域 */
}
.rate-half [type="radio"]:nth-child(even){
    -webkit-mask-position: right;
}


以上源码可查看 codepen点击预览


更好的实现


上面的实现已经非常完美了,无需 js,兼容性也不错,但是还是有些需要改进的地方,比如


  1. html 修改起来比较麻烦,从 5 分制改成 10 分制需要改动每一个 inputvalue 属性
  2. 星星的数量修改起来比较麻烦,从 5 颗星改成 10 颗星,需要手动添加新的 input 节点
  3. js 获取当前值相对比较麻烦,需要使用


document.quertSelector([type="radio"][name="rate"]:checked).value


不太符合语意,新手可能不太熟悉


那么,还有没有其他方式可以实现类似的效果呢?可以这样想一下,评分组件本质上也是一个表单输入组件,不考虑样式的话,普通的 input 也完全可以实现同样的输入功能,只是需要手动键盘输入而已。那有没有不需要手动输入的只需要简单拖拽就可以完成表单输入的呢?答案就是 input range范围选择器

<input type="range">


image.png

范围选择器有一些默认值


  1. 最小值 min0
  2. 最大值 max100
  3. 默认值 value 为区域范围的一半,50
  4. 默认步长 step1


现在和上面的评分组件对照起来,就可以很容易得出下面的结构

<input type="range" max="5" value="0">


这是一个可以输入 0~5 整数的组件,不能有小数出现,功能已经完全满足了


input range 的 shadow dom


现在可以开始改造样式了,在开始之前,可以在 Chrome 中开启 shadow-dom 的显示,方式为在控制台右上角 setting > preferences > Elements,勾选 Show user agent shadow DOM (已经勾选过的可以忽略)


image.png

这时便可很清楚的看到 input range 的内部结构

image.png

这里一共有三层,分别对应的选择器如下


  1. 容器 input[type="range" i]::-webkit-slider-container
  2. 轨道 input[type="range" i]::-webkit-slider-runnable-track
  3. 滑块 input[type="range" i]::-webkit-slider-thumb


input range 自定义样式


很多时候默认样式不是我们需要的,而且也不易修改,比如 Chrome 中 input range 的已滑动蓝色区域,这部分是无法修改的(Firefox 可以,这里只针对于 Chrome,其他浏览器原理类似),首先要做的就是重置

input[type="range"]{
    -webkit-appearance: none;
}


然后,由于滑块也是占据空间的,为了消除这个影响,可以将宽度设置为 0,当然也需要重置默认样式

input[type="range" i]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 0;
}


由于 Chrome 没有已滑动区域和未滑动区域的区分,这里采用足够大的 box-shadow 来覆盖实现

input[type="range" i]::-webkit-slider-thumb {
    box-shadow: 999px 0px 0px 999px #E8EAED; /*可以实现一个右侧的足够大的阴影*/
}


通过以上几步可以实现一个这样的效果,可以先停下来体会一下?

image.png

最后一步,加上 mask 遮罩,实现镂空效果

input[type="range" i]::-webkit-slider-runnable-track {
    -webkit-mask: url("data:image/svg+xml,%3Csvg width='12' height='11' viewBox='0 0 12 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 0l1.693 3.67 4.013.476L8.74 6.89l.788 3.964L6 8.88l-3.527 1.974.788-3.964L.294 4.146l4.013-.476L6 0z' fill='%23F67600'/%3E%3C/svg%3E"); /*星星图案*/
    -webkit-mask-size: 20px;
    -webkit-mask-repeat: repeat-x;
}


用一张动图描述一下效果

image.png

以上源码可查看 codepen点击预览


相关扩展


那么,现在来扩展一下,比如实现一个 10 颗星,总分 100 分的评分组件,支持半颗星,实现如下

<input type="range" max="100" value="0" step="5">


还需要修改一下宽度

input[type="range"]{
    width: 200px; /*每个星星的尺寸为20 * 20,10颗星宽度就是200*/
}


这样就得到了一个 10 颗心的评分组件

image.png

有时候,如果需要仅仅作为展示,比如一些电影评分,添加 disabled 就可以了~

<input type="range" value="0" step="0.5" disabled>


image.png

Firefox 的兼容


Firefox 也可以采用类似的原理,只需要换上对应的选择器即可,如下


  1. 容器 input[type=range](没有单独的容器,用最外层代替)
  2. 轨道 input[type=range]::-moz-range-track
  3. 滑块 input[type=range]::-moz-range-thumb


但是,Firefox 还有一个表示进度的选择器 ::-moz-range-progress,这样就可以不用 box-shadow 来遮盖了,所以,针对 Firefox 的另一种实现方式如下

input[type=range]{
    -webkit-mask: url("data:image/svg+xml,%3Csvg width='12' height='11' viewBox='0 0 12 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 0l1.693 3.67 4.013.476L8.74 6.89l.788 3.964L6 8.88l-3.527 1.974.788-3.964L.294 4.146l4.013-.476L6 0z' fill='%23F67600'/%3E%3C/svg%3E");
    -webkit-mask-size: 20px;
    -webkit-mask-repeat: repeat-x;
    height: 20px;
}
input[type=range]::-moz-range-track{
    background: #E8EAED;
    height: inherit;
}
input[type=range]::-moz-range-progress {
    background: coral;
    height: inherit;
}
input[type=range]::-moz-range-thumb {
    width: 0;
    opacity: 0;
}


动图演示如下

image.png

可以看到这里是单独的两层,和 Chrome 用阴影遮挡可不一样,因此可以实现更加灵活的效果,比如不依赖 mask 遮罩,分别设置不同的背景,下面是示意代码

input[type=range]::-moz-range-track{
    background: 未选中的星星;
    height: inherit;
}
input[type=range]::-moz-range-progress {
    background: 选中的星星;
    height: inherit;
}


这样,在 Firefox 中这样的效果也能实现(前提是上面的可以遮挡住下面的)

image.png

最后


首先说说这个方式的优点


  1. html 结构非常简单,就一个 input 元素
  2. 属性修改非常方便,只用设置 stepmax等相关属性即可
  3. 语义非常友好,默认表单提交也天然支持
  4. 当然键盘切换也是没有问题
  5. 移动端支持友好,可以滑动选择
  6. js 获取评分也符合常规表单,只需要 input.value 即可


当然,还是有一些局限


  1. IE 支持不够友好,理论上也是可以兼容的,尝试了一下在滑动的过程中会闪烁,体验不佳
  2. 没有 hover 样式


所以,如果你的项目不需要关照 IE ,大可以使用 input range 的方式~

相关文章
N..
|
6月前
|
开发框架 前端开发 UED
Bootstrap的CSS组件
Bootstrap的CSS组件
N..
65 0
|
21天前
|
前端开发 JavaScript
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
110 1
|
28天前
|
数据可视化 前端开发 搜索推荐
FLEX组件可视化设计器CSS3代码生成器
FLEX组件可视化设计器CSS3代码生成器
20 4
|
28天前
|
数据可视化 前端开发 数据安全/隐私保护
DIY可视化快速组件CSS样式设计生成源码
DIY可视化快速组件CSS样式设计生成源码
24 0
|
1月前
|
前端开发 JavaScript
uniapp纯CSS实现圆形进度条组件
uniapp纯CSS实现圆形进度条组件
75 0
|
3月前
|
前端开发 JavaScript
|
4月前
|
编解码 前端开发 容器
CSS Flex布局实战案例:构建响应式卡片组件
【7月更文挑战第17天】通过上述步骤,我们成功地使用CSS Flex布局构建了一个响应式的卡片组件。Flexbox不仅简化了布局代码,还让我们能够轻松实现复杂的布局效果,如响应式设计。在实战中,掌握Flexbox将大大提高前端开发的效率和网页布局的质量。希望这个案例能够帮助你更好地理解和应用Flexbox布局。
|
4月前
|
开发框架 前端开发 JavaScript
循序渐进BootstrapVue,开发公司门户网站(2)--- 使用wow.js动画组件以及自定义的CSS样式处理动态效果
循序渐进BootstrapVue,开发公司门户网站(2)--- 使用wow.js动画组件以及自定义的CSS样式处理动态效果
|
4月前
|
JavaScript 前端开发 容器
vue组件封装——固定宽高比的容器(2种方法:纯CSS实现 + JS实现)
vue组件封装——固定宽高比的容器(2种方法:纯CSS实现 + JS实现)
186 2
|
6月前
|
前端开发 JavaScript
vue组件制作专题 - (mpvue专用)在mpvue中手写css实现简单左右轮播
vue组件制作专题 - (mpvue专用)在mpvue中手写css实现简单左右轮播
46 0