使用SVG和CSS制作一个评星组件

349 阅读2分钟

不使用图片,如何构建一个星级/评级组件,支持像4.2或3.7这样的小数值? 这篇文章教你如何只使用CSS和内联svg,来制作评级分数星星组件。

笔者在一个电商公司工作,最近有个需求,需要做一个组件来显示客户评级。老版本用的是多个png图片叠加在一起,导致了不必要的请求和CLS 问题。新组件的标准是:

  • 使用内联SVG而不是图片;
  • 星星的数量支持动态;
  • 需要支持小数值;

最终组件

下面是我们的组件:

image.png

代码:

import IconStar from 'star.svg';

const Rating = ({ value, max, className }) => {
    /* 计算星星有多少需要被覆盖 */
  const percentage = Math.round((value / max) * 100);

  return (
    <div className={styles.container}>
    {
      /* 用map渲染一个基于最大评分值的 IconStar 数组 */
    }
      {Array.from(Array(max).keys()).map((_, i) => (
        <IconStar key={i} className={styles.star} />
      ))}
    {
      /* 把div放到星星组件的上面,用来遮住不需要显示的部分 */
    }
      <div className={styles.overlay} style={{ width: `${100 - percentage}%` }} />
    </div>
  );
}

组件由两部分组成:

  1. 基于最大评分星级的SVG图标列表(参数max);
  2. 一个覆盖在星星上的div,负责改变下面的星星的颜色。这就是小数部分工作的魔力。

覆盖在上面就是个普通的div,大小和下面不同的颜色/未填充的星星部分相同。我们计算div的宽度,首先用最大值除以评级,然后从100中减去这个值。

const percentage = Math.round((value / max) * 100);

<div className={styles.overlay} style={{ width: `${100 - percentage}%` }} />

添加下面的样式,给星星布局:

.container {
  display: inline-flex;
  align-items: center;
  position: relative;
}

.star {
  width: 18px;
  margin-right: 2px;
  display: flex;
  color: #f8d448;

  &:last-of-type {
    margin-right: 0;
  }
}

.overlay {
  background-color: black;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
}

现在覆盖的是纯黑色,让我们来改变它的颜色:

使用 mix-blend-mode 来改变SVG颜色

最后一步,使覆盖的 div 只影响下面的星形 svg,而不要影响到背景。我们可以使用 CSS 的 mix-blend-mode 混合模式属性和颜色值来做。

颜色说明如下: 使用源色的色相和饱和度以及背景色的亮度创建一个颜色。这保留了背景的灰度,对单色/彩色图形上色很有用。

我们用这个属性给图形上色,看看会发生什么:

.overlay {
  background-color: black;
  mix-blend-mode: color;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
}

这正是我们想要的! 它改变了星星的色调和饱和度,但保持背景色不变。你可以用背景色来改变星星的颜色。例如,如果我使用background-color: red,我得到的是红色而不是灰色的星星。

浏览器的支持度非常好(所有现代浏览器都支持),但对于较老的浏览器,我们可以改用 opacity:

.overlay {
  background-color: black;
  mix-blend-mode: color;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1;

  @supports not(mix-blend-mode: color) {
    opacity: 0.7;
  }
}

你可以在 这里 找到完整的源代码。如果你觉得这个 CSS 技巧有用,请动动手指点个赞,非常感谢!