在点赞和社交统计的世界里,评论是留下反馈的重要方法。用户通常喜欢在决定自己购买商品或阅读文章、观看电影或选择餐厅之前,了解其他人的意见。
开发人员经常在评论方面遇到困难——常见的情况是实现方式不可访问且过于复杂。嘿,CSS-Tricks 有一个 片段,现在已经接近十年了。
让我们一起探讨这种经典设计模式的新颖、可访问且可维护的方法。我们的目标是定义需求,然后踏上思考过程,并考虑如何实现这些需求。
确定工作范围
您知道使用星星作为评级可以追溯到1844年,当时它们在Murray's Handbooks for Travellers中首次用于对餐厅进行评级——后来在1931年由米其林指南推广为三星系统吗?这里有很多历史,所以难怪我们习惯了看到它!
它们经受住了时间的考验,有几个很好的理由。
- 清晰的视觉效果(以一行五个空心或实心星形呈现)
- 简单的标签(提供可访问的描述,例如
aria-label
)
当我们在网络上实现它时,重要的是我们专注于满足这两个结果。
以尽可能通用的方式实现此类功能也很重要。这意味着我们应该尽可能地使用 HTML 和 CSS,并尽量避免使用 JavaScript。这是因为
- JavaScript 解决方案在每个框架中都会有所不同。在原生 JavaScript 中常见的模式在框架中可能是反模式(例如,React 禁止直接操作文档)。
- 像 JavaScript 这样的语言发展迅速,这对社区来说很棒,但对于像这样的文章来说就不那么好了。我们希望找到一个可维护且长期有效的解决方案,因此我们应该根据一致、稳定的工具做出决策。
创建视觉效果的方法
CSS 的众多优点之一是,通常有很多方法可以写相同的东西。好吧,我们如何处理绘制星星也是一样的。我看到了五种选择。
- 使用图像文件
- 使用背景图像
- 使用 SVG 绘制形状
- 使用 CSS 绘制形状
- 使用 Unicode 符号
选择哪一个?这取决于情况。让我们检查一下所有选项。
方法 1:使用图像文件
使用图像意味着创建元素——至少需要 5 个。即使我们在五星评级中为每个星形调用相同的图像文件,那也是 5 个总请求。这样做的后果是什么?
- 更多的 DOM 节点使文档结构更加复杂,这可能导致页面绘制速度变慢。元素本身也需要渲染,这意味着服务器响应时间(如果为 SSR)或主线程生成(如果我们在 SPA 中工作)必须增加。这甚至还没有考虑必须实现的渲染逻辑。
- 它不处理分数评级,比如 5 星中的 2.3 星。这将需要一组重复的元素,并在其顶部使用
clip-path
进行遮罩。这将使文档的复杂性至少增加 7 个 DOM 节点,并且可能增加数十个额外的 CSS 属性声明。 - 优化的性能应该考虑图像的加载方式,并在添加重复元素时,实现类似于延迟加载)等功能变得越来越困难。
- 它会发出请求,这意味着应该配置缓存TTL以实现即时的第二个图像加载。但是,即使正确配置了此设置,第一次加载仍然会受到影响,因为需要等待服务器的TTFB。为了优化图像的第一次加载,应考虑预取、预连接技术或服务工作者。
- 它为屏幕阅读器创建了至少五个无意义的元素。正如我们之前讨论的那样,标签比图像本身更重要。没有理由将它们留在 DOM 中,因为它们不会为评级添加任何意义——它们只是一个常见的视觉效果。
- 图像可能是可管理媒体的一部分,这意味着内容管理员可以随时更改星形的外观,即使它是错误的。
- 它允许星形具有多样的外观,但是**活动状态可能仅与初始状态相似**。无法在没有 JavaScript 的情况下更改图像
src
属性,而这是我们正在努力避免的。
想知道 HTML 结构可能是什么样子吗?可能是这样的。
<div class="Rating" aria-label="Rating of this item is 3 out of 5">
<img src="/static/assets/star.png" class="Rating--Star Rating--Star__active">
<img src="/static/assets/star.png" class="Rating--Star Rating--Star__active">
<img src="/static/assets/star.png" class="Rating--Star Rating--Star__active">
<img src="/static/assets/star.png" class="Rating--Star">
<img src="/static/assets/star.png" class="Rating--Star">
</div>
为了更改这些星形的外观,我们可以使用多个 CSS 属性。例如
.Rating--Star {
filter: grayscale(100%); // maybe we want stars to become grey if inactive
opacity: .3; // maybe we want stars to become opaque
}
此方法的另一个好处是,<img>
元素默认设置为inline-block
,因此将其排列在同一行只需要稍微少一些样式。
方法 2:使用背景图像
这曾经是一种相当常见的实现方式。也就是说,它仍然有其优缺点。
例如
- 当然,它只有一个服务器请求,这减轻了许多缓存需求。同时,我们现在必须等待三个额外的事件才能显示星形:即(1)下载 CSS,(2)解析 CSSOM,以及(3)下载图像本身。
- 将星形的状态从空心更改为实心非常容易,因为我们实际上只是更改了背景图像的位置。但是,每次需要更改星形实际外观时,都必须打开图像编辑器并重新上传文件,这在维护方面并不是最理想的事情。
- 我们可以使用 CSS 属性,如
background-repeat
属性和clip-path
来减少 DOM 节点的数量。从某种意义上说,我们可以使用单个元素来实现这一点。另一方面,我们没有技术上好的可访问标记来识别屏幕阅读器的图像并使星形被识别为输入,这一点并不好。好吧,不容易。
在我看来,背景图像可能最适合用于复杂星形外观,在这些外观中,CSS 和 SVG 都无法获得确切的样式。否则,使用背景图像仍然会带来很多折衷方案。
方法 3:使用 SVG 绘制形状
SVG 太棒了!它具有与光栅图像相同的许多自定义绘图优点,但如果内联则不需要服务器调用,因为,好吧,它只是代码!
我们可以将五个星形内联到 HTML 中,但我们可以做得更好,对吧?Chris 向我们展示了一种不错的方法,它允许我们将单个形状的 SVG 标记作为<symbol></symbol>
提供,并使用<use></use>
多次调用它。
<!-- Draw the star as a symbol and remove it from view -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="star" viewBox="214.7 0 182.6 792">
<!-- <path>s and whatever other shapes in here -->
</symbol>
</svg>
<!-- Then use anywhere and as many times as we want! -->
<svg class="icon">
<use xlink:href="#star"></use>
</svg>
<svg class="icon">
<use xlink:href="#star"></use>
</svg>
<svg class="icon">
<use xlink:href="#star"></use>
</svg>
<svg class="icon">
<use xlink:href="#star"></use>
</svg>
<svg class="icon">
<use xlink:href="#star"></use>
</svg>
好处是什么?好吧,我们说的是零请求、更简洁的 HTML、无需担心像素化以及开箱即用的可访问属性。此外,我们还可以灵活地在任何地方使用星形,并且可以根据需要使用任意次数的星形,而不会对性能造成任何额外损失。得分!
最大的好处是,这也不需要额外的开销。例如,我们不需要构建过程来实现这一点,也不依赖于额外的图像编辑软件来进行未来的更改(尽管,老实说,它确实有所帮助)。
方法 4:使用 CSS 绘制形状
此方法与background-image
方法非常相似,但通过使用 CSS 属性优化绘制形状而不是调用图像来对其进行改进。我们可能会将 CSS 视为使用边框、字体和其他内容来设置元素样式,但它也能够产生一些非常复杂的艺术作品。看看Diana Smith 的现在著名的“Francine”肖像。

我们不会变得那么疯狂,但您可以看到我们的目标。事实上,这里在 CSS-Tricks 上已经有了一个CSS 星形形状的不错的演示。
查看 Geoff Graham 的 CodePen 上的示例
五颗星!(@geoffgraham)
在 CodePen 上。
或者,我们可以使用clip-path
属性绘制一个五角星形多边形,变得更巧妙一些。更少的 CSS!但是,买家要注意,因为您的跨浏览器支持里程可能会有所不同。
查看 Geoff Graham 的 CodePen 上的示例
5 个裁剪的星星!(@geoffgraham)
在 CodePen 上。
方法 5:使用 Unicode 符号
此方法非常好,但在外观方面非常有限。为什么?因为星星的外观作为 Unicode 字符被固定了。但是,嘿,有填充星(★)和空星(☆)的变体,这正是我们需要的!
Unicode 字符是可以直接复制粘贴到 HTML 中的内容
查看 Geoff Graham 的 CodePen 上的示例
Unicode 星星!(@geoffgraham)
在 CodePen 上。
我们可以使用字体、颜色、宽度、高度和其他属性来调整大小和设置样式,但灵活性不大。但这可能是其中最基本的 HTML 方法,以至于它看起来几乎过于明显了。
相反,我们可以将内容移动到 CSS 中作为伪元素。这将释放额外的样式功能,包括使用自定义属性来部分填充星星
查看 Fred Genkin 的 CodePen 上的示例
微小但可访问的五星级评分(@FredGenkin)
在 CodePen 上。
让我们更详细地分解最后一个示例,因为它最终从其他方法中获得了最佳优势,并将它们拼接成一个几乎没有缺点的单一解决方案,同时满足了我们的所有需求。
让我们从 HTML 开始。有一个单独的元素,它不调用服务器,同时保持可访问性
<div class="stars" style="--rating: 2.3;" aria-label="Rating of this product is 2.3 out of 5."></div>
如您所见,评分值作为内联自定义 CSS 属性(--rating
)传递。这意味着除了在标签中显示相同的评分值以提高可访问性之外,不需要额外的渲染逻辑。
让我们看看那个自定义属性。它实际上是从值值到百分比的转换,在 CSS 中使用calc()
函数处理
--percent: calc(var(--rating) / 5 * 100%);
我选择走这条路是因为 CSS 属性(如width
和linear-gradient
)不接受<number></number>
值。他们接受<length></length>
和<percentage></percentage>
,并在其中包含特定的单位,如%
和px
、em
。最初,评分值是浮点数,它是<number></number>
类型。使用此转换有助于确保我们能够以多种方式使用这些值。
填充星星听起来可能很困难,但事实证明非常简单。我们需要一个linear-gradient
背景来创建金色填充应结束的硬色停靠点
background: linear-gradient(90deg,
var(--star-background) var(--percent),
var(--star-color) var(--percent)
);
请注意,我正在使用自定义变量来表示颜色,因为我希望样式易于调整。由于自定义属性是从父元素样式继承的,因此您可以对:root
元素定义一次,然后在元素包装器中覆盖。以下是我在根元素中放置的内容
:root {
--star-size: 60px;
--star-color: #fff;
--star-background: #fc0;
}
我做的最后一件事是将背景裁剪到文本的形状,以便背景渐变采用星星的形状。将 Unicode 星星视为我们用来从背景颜色中剪切星星形状的模板。或者像用星星形状的饼干切割器直接压入面团一样
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
对背景裁剪和文本填充的浏览器支持非常好。IE11是唯一一个坚持使用旧方法的浏览器。
最终想法
图像文件 | 背景图像 | SVG | CSS 形状 | Unicode 符号 | |
---|---|---|---|---|---|
可访问性 | ★★☆☆☆ | ★★★☆☆ | ★★★★★ | ★★★★★ | ★★★★★ |
管理 | ★★★★☆ | ★★★★☆ | ★★☆☆☆ | ★★☆☆☆ | ★★☆☆☆ |
性能 | ★☆☆☆☆ | ★★☆☆☆ | ★★★★★ | ★★★★★ | ★★★★★ |
维护 | ★★★★☆ | ★★★☆☆ | ★★★★☆ | ★★☆☆☆ | ★★★★★ |
总体 | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ | ★★★☆☆ | ★★★★★ |
在我们介绍的五种方法中,我最喜欢两种:使用 SVG(方法 3)和在伪元素中使用 Unicode 字符(方法 5)。在某些情况下,背景图像确实很有意义,但这似乎最好根据具体情况进行评估,而不是作为首选解决方案。
您必须始终考虑特定方法的所有优点和缺点。在我看来,这就是前端开发的魅力所在!有多种方法可以实现,需要适当的经验才能有效地实现功能。
精彩的文章,您还可以使用 CSS Paint API 为星级评分创建背景 - https://bobrov.dev/css-paint-demos/star-rating/
SVG 解决方案有一点我不喜欢:它使用 CSS 隐藏了参考元素。
在理想情况下,第一次出现将是参考,所有后续出现都将通过引用使用原始参考。
在更现实的情况下,我认为使用
<defs>
元素会是一个更好的解决方案,因为它更具语义,并且不需要 CSS。我真的很喜欢 Unicode 符号用于显示评分星。但我希望看到您将如何将其用于表单输入,例如给出 2.3 的评分?您能帮我解决这个问题吗?
很棒的 CSS-TRICK。它运行良好。但是,我可以在一个页面中使用多个吗?我的意思是,我想使用此功能来评价一些化妆品,所有这些化妆品都显示在一个页面上。我知道有时如果在一个页面中多次调用 JS 文件,它们会崩溃。
最少的评分星(只读)