CSS 饼图计时器

Avatar of Kitty Giraudel
Kitty Giraudel 发布

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

编辑注: Kitty Giraudel 在 CodePen 上制作了一个很酷的“饼图计时器”演示。 在 CSS 中实现这个功能并不直观或简单。 然后,他们给我发来了一篇关于他们如何实现它的文章,我敢说这绝对是一篇很棒的 CSS-Tricks 文章。 谢谢,Kitty!

我们在做什么?

您可能已经看到过一些这样的加载器,它们主要出现在 Flash 网站上。 它基本上是一个饼图,它越来越大,最终变成一个完整的圆圈。

起初,我认为这将非常容易,只需创建一个圆圈,让它旋转,在遮罩后面隐藏一部分,就完成了。 然而,结果证明这比想象的要困难得多。 事实上,即使使用像 Sass 和 Compass 这样的预处理器,CSS 也无法完成这样的任务。 当涉及到制作形状时,我们总是会遇到困难,更不用说样式化或动画化这些形状了。 很多时候,我们可以找到解决方法,并让它运行起来,但代价是可维护性或语义性。

为什么要这样做?

我认为最常见的用例是计时器。 但这些概念也可以用于仅用纯 CSS 制作饼图。 如果您是

即使有很多很棒的工具可以管理饼图(主要使用 JavaScript),我们可能可以轻松地想出如何仅用 CSS 制作饼图,甚至使用这样的技巧来制作这些饼图的动画。
Atomic Noggin Enterprise 网站上有一篇关于使用 clip 属性制作 CSS 饼图的教程

好吧,这是一个语义不好的解决方法! 但是可维护性还不错,所以我们开始吧。

HTML 代码

我们需要 3 个不同的元素

  • 一个旋转器: 这是在整个过程中旋转的半圆
  • 一个遮罩: 这是在动画的前 50% 期间隐藏旋转器的元素
  • 一个填充器: 这是在动画的后 50% 期间完成圆圈的元素

我们需要将所有这些元素放在同一个父元素中,以便允许绝对定位

<div class="wrapper">
<div class="pie spinner"></div>
<div class="pie filler"></div>
<div class="mask"></div>
</div>
 

由于旋转器和填充器是同一个圆圈的两个半部分,我们使用一个共享的类(.pie)来为它们设置样式。

 
CSS 代码

为了保持本文代码的简洁和易懂,我们不会添加任何供应商前缀。

父元素为计时器设置大小和绝对定位上下文

.wrapper {
  width: 250px;
  height: 250px;
  position: relative;
  background: white;
}

确保宽度和高度相等,以便制作圆圈,并确保整个过程正常工作。

“旋转器”和“填充器”共享此 CSS 代码

.pie {
  width: 50%;
  height: 100%;
  position: absolute;
  background: #08C;
  border: 10px solid rgba(0,0,0,0.4);
}

它们的宽度等于父元素宽度的 50%,因为它们都是同一个圆圈的一部分,它们的高度与父元素高度相同。 我们还为它们添加了一些颜色和边框,以便正确地识别它们。

“旋转器”

.spinner {
  border-radius: 125px 0 0 125px;
  z-index: 200;
  border-right: none;
  animation: rota 10s linear infinite;
}

我们必须让它看起来像一个半圆,在左上角和左下角使用圆角。 此外,我们为它提供了一个较高的正z-index,以便将它放在填充器上面,但在遮罩后面。

然后我们添加animation,时长为 10 秒。 我们稍后将详细讨论动画。

“填充器”

.filler {
  border-radius: 0 125px 125px 0;
  z-index: 100;
  border-left: none;
  animation: fill 10s steps(1, end) infinite;
  left: 50%;
  opacity: 0;
}

对于旋转器,我们设置了border-radiusz-index,删除了border-left,并将animation 时长设置为 10 秒。 对于此元素,animation-timing-function 并非设置为linear,而是设置为steps(1, end)。 这意味着animation 不会从 0% 到 100% 逐步进行,而是一次性完成。

由于填充器在动画的前半部分不可见,我们将它的不透明度设置为 0,并将它的位置设置为父元素宽度的 50%。

“遮罩”

.mask {
  width: 50%;
  height: 100%;
  position: absolute;
  z-index: 300;
  opacity: 1;
  background: inherit;
  animation: mask 10s steps(1, end) infinite;
}

遮罩从动画开始就存在,所以它的不透明度设置为 1,它的背景从父元素的背景颜色继承(使其不可见)。 为了覆盖旋转器,它与旋转器具有相同的尺寸,它的z-index 设置为 300。

关键帧

@keyframes rota {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

@keyframes fill {
  0%        { opacity: 0; }
  50%, 100% { opacity: 1; }
}

@keyframes mask {
  0%        { opacity: 1; }
  50%, 100% { opacity: 0; }
}

第一个动画(rota)是为旋转器准备的。 它在 10 秒内从 0 度到 360 度逐步旋转。

第二个动画(fill)是为填充器准备的。 它在 5 秒后立即从 0 不透明度到 1 不透明度。

最后一个动画(mask)是为遮罩准备的。 它在 5 秒后立即从 1 不透明度到 0 不透明度。

所以动画看起来像这样

  • T0 – 旋转器在左侧,被遮罩隐藏。 填充器被隐藏。
  • T1 – 旋转器开始顺时针旋转,并逐渐从遮罩后面出现。
  • T2 – 旋转器旋转了 360/10*2 = 72 度,并继续旋转。
  • T3 – 旋转器旋转了 360/10*3 = 108 度,并继续旋转。
  • T4 – 旋转器旋转了 360/10*4 = 144 度,并继续旋转。
  • T5 – 旋转器旋转了 360/10*5 = 180 度,并继续旋转。 在此时,填充器立即变为 100% 不透明度,而遮罩消失。
  • T6 – 旋转器旋转了 360/10*6 = 216 度,并继续旋转。
  • T7 – 旋转器旋转了 360/10*7 = 252 度,并继续旋转。
  • T8 – 旋转器旋转了 360/10*8 = 288 度,并继续旋转。
  • T9 – 旋转器旋转了 360/10*9 = 324 度,并继续旋转。
  • T10 – 旋转器旋转了 360 度,回到起点。 然后我们重新开始动画。 遮罩变为 100% 不透明度,而填充器消失。

奖励

以下是一些额外的技巧,它们可能会非常酷,具体取决于您的需求。

悬停暂停

.wrapper:hover .filler,
.wrapper:hover .spinner,
.wrapper:hover .mask {
  animation-play-state: paused;
}

使用此代码片段,您可以通过将鼠标悬停在父元素上,来使整个动画暂停。

内部内容

借助z-index,我们可以轻松地在旋转器内部添加一些内容,并让它以相同的方式旋转。 尝试将以下代码片段添加到您的代码中

.spinner:after {
  content: "";
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  top: 10px;
  right: 10px;
  background: #fff;
  border: 1px solid rgba(0,0,0,0.4);
  box-shadow: inset 0 0 3px rgba(0,0,0,0.2);
}

预处理器或 CSS 变量

目前,维护起来并不容易。但如果我们使用变量(预处理器或即将推出的原生 CSS 变量),我们可以更容易地维护。例如,您可以添加一个变量来管理持续时间,而无需在 3 个动画声明中进行更改。
如果您想在不使用预处理器的情况下简化可维护性,我猜您可以创建一个只处理动画持续时间的类,并将该类添加到 3 个子元素中。它看起来像这样

.animation-duration {
  animation-duration: 10s;
}

缺点

遗憾的是,这种技术无法做到一些事情。

  • 不支持渐变(看起来很糟糕)
  • 不支持阴影(看起来很脏)
  • 不完全响应。如果更改父元素的尺寸,除了边框半径之外,所有内容都会正常。您仍然需要手动更改边框半径值,因为除非我们处理正方形,否则我们无法设置 50% 的边框半径。
  • 不是语义化的(一个动画有 4 个元素)。即将推出的多个伪元素可能会解决这个问题,或者使用 Web Components。

浏览器支持

由于我们使用的是 CSS 动画和关键帧,因此浏览器支持率很低,但会随着时间的推移而提高。目前,唯一支持 CSS 动画的浏览器是

  • Internet Explorer 10
  • Firefox 12+
  • Chrome
  • Safari 5+
  • Opera 12+
  • iOS Safari 3+
  • Android 2+(在 v4 之前存在 bug)

演示

在 CodePen 上查看演示