使用 CSS 自定义属性构建骨架屏幕

Avatar of Max Böck
Max Böck

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

设计网页上的加载状态经常被忽视或作为事后才想到的东西。性能不仅仅是开发人员的责任,构建一个能够在慢速连接下正常工作的体验也是一项设计挑战。

虽然开发人员需要关注诸如缩小和缓存之类的事情,但设计师必须考虑 UI 在“加载”或“离线”状态下的外观和行为。

速度的错觉

随着我们对移动体验的期望发生变化,我们对性能的理解也在发生变化。人们期望网页应用程序感觉起来与原生应用程序一样敏捷和响应迅速,无论其当前网络覆盖范围如何。

感知性能 是衡量某件事对用户感觉有多快的指标。其理念是,如果用户知道发生了什么,并且可以在内容实际出现之前预料到内容,那么他们会更有耐心,并且会认为系统更快。这很大程度上是关于管理期望和让用户保持知情。

对于网页应用程序,这个概念可能包括显示文本、图像或其他内容元素的“模型”——称为骨架屏幕 💀。您可以在现实世界中找到这些内容,被 Facebook、Google、Slack 等公司使用。

Slack desktop app using skeleton screens while loading
Slack,您也是太棒了。
Facebook 的骨架

示例

假设您正在构建一个网页应用程序。它是一种旅行建议类型的应用程序,人们可以在其中分享他们的旅行并推荐地点,因此您的主要内容可能看起来像这样。

card UI of a travel blog post

您可以将该卡片简化为其基本视觉形状,即 UI 组件的骨架

skeleton version of the same card, outlined in gray rectangles

每当有人从服务器请求新内容时,您都可以立即开始显示骨架,同时在后台加载数据。内容准备就绪后,只需将骨架替换为实际卡片即可。这可以通过纯 JavaScript 或使用 React 之类的库来完成。

现在,您可以使用图像来显示骨架,但这将引入额外的请求和数据开销。我们已经在加载内容,因此等待另一个图像先加载不是一个好主意。此外,它没有响应能力,如果我们将来决定调整内容卡片的一些样式,我们将不得不将更改复制到骨架图像中,以便它们再次匹配。😒 糟糕。

更好的解决方案是仅使用 CSS 创建整个内容。没有额外的请求,最小的开销,甚至没有额外的标记。而且我们可以通过一种方式来构建它,这样将来更容易更改设计。

在 CSS 中绘制骨架

首先,我们需要绘制构成卡片骨架的基本形状。我们可以通过向 background-image 属性添加不同的 渐变 来做到这一点。默认情况下,线性渐变从上到下运行,具有不同的颜色停止过渡。如果我们只定义一个颜色停止点并将其余部分保留为透明,我们就可以绘制形状。

请记住,多个背景图像在此处叠加在一起,因此顺序很重要。最后一个渐变定义将在后面,第一个在前面。

.skeleton {
  background-repeat: no-repeat;
  background-image: 
    /* layer 2: avatar */
    /* white circle with 16px radius */
    radial-gradient(circle 16px, white 99%, transparent 0),
    /* layer 1: title */
    /* white rectangle with 40px height */
    linear-gradient(white 40px, transparent 0),
    /* layer 0: card bg */
    /* gray rectangle that covers whole element */
    linear-gradient(gray 100%, transparent 0);
}

这些形状像常规块级元素一样拉伸以填充整个空间。如果我们想改变这一点,我们将不得不为它们定义显式尺寸。background-size 中的值对设置每个图层的宽度和高度,保持我们在 background-image 中使用的相同顺序。

.skeleton {
  background-size:
    32px 32px,  /* avatar */
    200px 40px, /* title */
    100% 100%;  /* card bg */
}

最后一步是在卡片上定位元素。这就像 position:absolute 一样,值表示 lefttop 属性。例如,我们可以模拟头像和标题的 24px 填充,以匹配真实内容卡片的外观。

.skeleton {
  background-position:
    24px 24px,  /* avatar */
    24px 200px, /* title */
    0 0;        /* card bg */
}

使用自定义属性拆分它

这在一个简单的示例中效果很好——但如果我们想构建一些稍微复杂的东西,CSS 会很快变得混乱且难以阅读。如果另一个开发人员获得了该代码,他们将不知道所有这些神奇数字是从哪里来的。维护它肯定很糟糕。

谢天谢地,我们现在可以使用 自定义 CSS 属性 以更简洁、更适合开发人员的方式编写骨架样式——甚至考虑到不同值之间的关系。

.skeleton {
  /*
    define as separate properties
  */
  --card-height: 340px;
  --card-padding:24px;
  --card-skeleton: linear-gradient(gray var(--card-height), transparent 0);

  --title-height: 32px;
  --title-width: 200px;
  --title-position: var(--card-padding) 180px;
  --title-skeleton: linear-gradient(white var(--title-height), transparent 0);

  --avatar-size: 32px;
  --avatar-position: var(--card-padding) var(--card-padding);
  --avatar-skeleton: radial-gradient(
    circle calc(var(--avatar-size) / 2), 
    white 99%, 
    transparent 0
  );

  /* 
    now we can break the background up 
    into individual shapes 
  */
  background-image: 
    var(--avatar-skeleton),
    var(--title-skeleton),
    var(--card-skeleton);

  background-size:
    var(--avatar-size),
    var(--title-width) var(--title-height),
    100% 100%;

  background-position:
    var(--avatar-position),
    var(--title-position),
    0 0;
}

这不仅更易读,而且以后更改某些值也更容易。此外,我们可以使用一些变量(比如 --avatar-size--card-padding 等)来定义实际卡片的样式,并始终使其与骨架版本保持同步。

现在,添加媒体查询以在不同的断点处调整骨架的某些部分也很简单。

@media screen and (min-width: 47em) {
  :root {
    --card-padding: 32px;
    --card-height: 360px;
  }
}

自定义属性的浏览器支持 很好,但并非 100%。基本上,所有现代浏览器都支持,IE/Edge 稍微晚了一些。对于这个特定的用例,添加使用 Sass 变量的回退很容易。

添加动画

为了使它变得更好,我们可以为我们的骨架添加动画,使其看起来更像一个加载指示器。我们需要做的就是在顶层放置一个新的渐变,然后使用 @keyframes 为其位置添加动画。

这是完成的骨架卡片可能看起来如何的完整示例。

骨架加载卡片 由 Max Böck (@mxbck) 在 CodePen 上创建。

您可以使用 :empty 选择器和伪元素来绘制骨架,因此它只适用于空卡片元素。一旦内容被注入,骨架屏幕将自动消失。

更多关于为性能设计的信息

要详细了解为感知性能设计,请查看以下链接。