在喜欢和社交统计的世界中,评论是留下反馈的一种非常重要的方法。用户往往喜欢先了解他人的意见,然后再决定自己购买的物品,甚至是看电影看的文章,或者就餐的餐厅。
开发人员经常与审查作斗争——通常会看到难以访问和过于复杂的实现。嘿,CSS-Tricks 有一个片段,它现在接近十年了。
让我们来看看这个经典设计模式的新的、可访问的和可维护的方法。我们的目标是定义需求,然后进行思考过程和考虑如何实现它们的旅程。
界定工作范围
您是否知道,使用星级作为评级的历史可以追溯到 1844 年,当时它们首次在Murray 的《旅行者手册》中用于对餐厅进行评级——后来在 1931 年被米其林指南推广为三星级系统?那里有很多历史,难怪我们已经习惯了!
他们经受住了时间的考验有几个很好的理由:
- 清晰的视觉效果(以连续五个空心或填充星的形式)
- 一个简单的标签(提供可访问的描述,如
aria-label
)
当我们在网络上实施它时,重要的是我们要专注于满足这两个结果。
以最通用的方式实现此类功能也很重要。这意味着我们应该尽可能多地使用 HTML 和 CSS,并尽量避免使用 JavaScript。那是因为:
- JavaScript 解决方案总是因框架而异。vanilla JavaScript 中的典型模式可能是框架中的反模式(例如,React 禁止直接文档操作)。
- 像 JavaScript 这样的语言发展很快,这对社区来说非常棒,但像这样的文章就不那么棒了。我们想要一个可维护且长期相关的解决方案,因此我们应该基于一致、稳定的工具做出决策。
创建视觉效果的方法
CSS 的许多美妙之处之一是,通常有多种方法可以编写相同的东西。好吧,同样的事情也适用于我们如何处理绘画明星。我看到了五个选项:
- 使用图像文件
- 使用背景图片
- 使用 SVG 绘制形状
- 使用 CSS绘制形状
- 使用 Unicode 符号
选择哪一个?这取决于。让我们检查一下。
方法一:使用图片文件
使用图像意味着创建元素——准确地说至少有 5 个。即使我们为五颗星评级中的每颗星调用相同的图像文件,这也是总共五个请求。这样做的后果是什么?
- 更多的 DOM 节点使文档结构更加复杂,这可能导致页面绘制速度变慢。元素本身也需要渲染,这意味着服务器响应时间(如果SSR)或主线程生成(如果我们在SPA中工作)必须增加。这甚至没有考虑必须实现的渲染逻辑。
- 它不处理分数评级,比如 2.3 星(满分 5 星)。这将需要第二组重复的元素
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
默认设置为,因此将它们放置在一行中需要更少的样式。
管理:★★★★☆
性能:★☆☆☆☆
维护:★★★★☆
总体:★★☆☆☆
方法二:使用背景图片
这曾经是一个相当普遍的实现。也就是说,它仍然有其优点和缺点。
例如:
- 当然,这只是一个单一的服务器请求,可以减轻很多缓存需求。同时,我们现在必须等待三个额外的事件才能显示星星:(1)要下载的 CSS,(2)要解析的CSSOM,以及(3)要下载的图像本身。
- 将星星的状态从空变为填充非常容易,因为我们真正要做的就是改变背景图像的位置。但是,就维护而言,在需要更改星星的实际外观时必须打开图像编辑器并重新上传文件并不是最理想的事情。
background-repeat
我们可以使用类似属性的CSS 属性clip-path
来减少 DOM 节点的数量。从某种意义上说,我们可以使用单个元素来完成这项工作。另一方面,我们在技术上没有良好的可访问标记来识别屏幕阅读器的图像并让星星被识别为输入,这并不是很好。嗯,不容易。
在我看来,背景图像可能是最常用的复杂星形外观,其中 CSS 和 SVG 都不足以降低确切的样式。否则,使用背景图像仍然会带来很多妥协。
管理:★★★★☆
性能:★★☆☆☆
维护:★★★☆☆
总体:★★★☆☆
方法三:使用SVG绘制形状
SVG很棒!它具有许多与光栅图像相同的自定义绘图优势,但如果它是内联的,则不需要服务器调用,因为它只是代码!
我们可以在 HTML 中内联五颗星,但我们可以做得更好,对吧?Chris 向我们展示了一种很好的方法,它允许我们为单个形状提供 SVG 标记作为 a<symbol></symbol>
并使用 with 多次调用它<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、不用担心像素化以及开箱即用的可访问属性。另外,我们可以灵活地在任何地方使用星星,并且可以根据需要多次使用它们,而不会对性能造成额外的影响。分数!
最终的好处是这也不需要额外的开销。例如,我们不需要构建过程来实现这一点,并且不依赖额外的图像编辑软件来进行进一步的更改(不过,老实说,它确实有帮助)。
管理:★★☆☆☆
性能:★★★★★
维护:★★★★☆
总体:★★★★☆
方法四:使用CSS绘制形状
此方法与方法非常相似background-image
,但通过使用 CSS 属性优化绘制形状而不是调用图像对其进行了改进。我们可能会认为 CSS 是带有边框、字体和其他东西的样式元素,但它也能够制作一些非常复杂的艺术品。看看戴安娜史密斯现在著名的“弗朗辛”肖像。
Francine,Diana Smith 用 CSS 完成的油画的 CSS 复制品(来源)
我们不会那么疯狂,但你可以看到我们的目标。事实上,在 CSS-Tricks 上已经有一个很好的 CSS 星形演示。
clip-path
或者,嘿,我们可以通过使用该属性绘制一个五点多边形来变得更加狡猾。更少的CSS!但是,买家要小心,因为您的跨浏览器支持里程可能会有所不同。
管理:★★☆☆☆
性能:★★★★★
维护:★★☆☆☆
总体:★★★☆☆
方法 5:使用 Unicode 符号
这种方法非常好,但在外观方面非常有限。为什么?因为星星的外观是一成不变的 Unicode 字符。但是,嘿,填充星(★)和空星(☆)有变化,这正是我们需要的!
Unicode 字符可以直接复制并粘贴到 HTML 中:
我们可以使用字体、颜色、宽度、高度和其他属性来调整大小和样式,但这里没有很大的灵活性。但这可能是这群人中最基本的 HTML 方法,它似乎太明显了。
相反,我们可以将内容作为伪元素移动到 CSS 中。这释放了额外的样式功能,包括使用自定义属性来填充星星:
让我们进一步分解最后一个例子,因为它最终从其他方法中获得了最大的好处,并将它们拼接成一个单一的解决方案,同时满足我们所有的要求。
让我们从 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>
and<percentage></percentage>
并在其中包含特定的单位,例如%
and 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)。肯定有背景图像很有意义的用例,但这似乎最好逐案评估,而不是首选解决方案。
您必须始终考虑特定方法的所有优点和缺点。在我看来,这就是前端开发的美妙之处!有多种方法可以走,并且需要适当的经验才能有效地实现功能。