如何只使用CSS创建一个圆形进度条?
圆形进度条是当今网页中非常常见的元素。然而,对许多开发者来说,它们似乎是一个相当令人生畏的挑战。事实上,通过一些新的CSS特性的帮助,基本的实现并不难。事实上,比以往更容易。
圆形进度条的结构
一个圆形进度条,简单来说,就是两个叠在一起的圆。底部圆是背景,顶部圆是进度指示器。我们稍后会讨论如何填充进度指示器,但是基本的结构可以使用<svg>
元素和一点CSS轻松构建。
<svg width="250" height="250" viewBox="0 0 250 250">
<circle class="bg"
cx="125" cy="125" r="115" fill="none" stroke="#ddd" stroke-width="20"
></circle>
<circle class="fg"
cx="125" cy="125" r="115" fill="none" stroke="#5394fd" stroke-width="20"
></circle>
</svg>
circle.fg {
transform: rotate(-90deg);
transform-origin: 125px 125px;
}
正如你所见,我们需要的唯一一段CSS代码来正确构建基本结构是一个transform
属性。我们将前景圆旋转90度,并将transform-origin
设置为圆的中心。这样,圆将围绕其中心旋转,进度指示器从顶部开始。
进行数学计算
在我们继续之前,我们可以花一点时间来理解代码背后的数学原理。
我们需要决定的两个值是进度条的大小和描边的宽度。对于这个例子,我们选择了大小为250px
和描边宽度为20px
。我们将使用这些值来计算我们需要的其他值。
size
用于设置SVG元素的width
和height
属性,以及viewBox
属性。将其除以2,我们得到125px
,这对应于图像的中心的坐标。这个值用于设置圆的cx
和cy
属性。
考虑到描边宽度,我们可以计算出圆的半径。半径是从圆心到边缘的距离。在这种情况下,半径是115px
,即图像的大小减去描边宽度再除以2。
最后,我们可以计算出圆的周长。周长是圆的边缘的长度。在这种情况下,周长是722.5px
,即2 * π * 115px
。
变量 | 值 | 公式 |
---|---|---|
size | 250px |
N/A(用户定义) |
stroke | 20px |
N/A(用户定义) |
center | 125px |
size / 2 |
radius | 115px |
(size - stroke) / 2 |
circumference | 722.5px |
2 * π * radius |
随着我们的进展,这些数字将变得有用,但我保证这几乎是我们需要做的所有数学计算。
填充进度指示器
现在我们已经有了基本的结构,我们需要填充进度指示器。为此,我们将使用stroke-dasharray
属性,该属性接受交替的长度和虚线值。
为了创建一个进度条,我们需要传递两个值:填充部分的长度和空白部分的长度。为了得到填充部分,我们将进度百分比乘以圆的周长。为了得到空白部分,我们从周长中减去填充部分。
假设我们想填充圆的50%,SVG代码将如下所示:
<svg width="250" height="250" viewBox="0 0 250 250">
<circle class="bg"
cx="125" cy="125" r="115" fill="none" stroke="#ddd" stroke-width="20"
></circle>
<circle class="fg"
cx="125" cy="125" r="115" fill="none" stroke="#5394fd" stroke-width="20"
stroke-dasharray="361.25 361.25"
></circle>
使进度条动态化
硬编码stroke-dasharray
的值并不是很有用。我们希望能够动态设置进度百分比。这就是CSS变量和之前的数学知识发挥作用的地方。
给定一个--progress
变量,我们可以相对容易地计算出stroke-dasharray
的值。知道我们将需要之前大部分的值,我们也可以将它们设置为CSS变量。更好的是,我们想要设置的大部分SVG属性都可以使用CSS进行操作。
下面是重构后的代码:
<svg
width="250" height="250" viewBox="0 0 250 250"
class="circular-progress" style="--progress: 50"
>
<circle class="bg"></circle>
<circle class="fg"></circle>
</svg>
.circular-progress {
--size: 250px;
--half-size: calc(var(--size) / 2);
--stroke-width: 20px;
--radius: calc((var(--size) - var(--stroke-width)) / 2);
--circumference: calc(var(--radius) * pi * 2);
--dash: calc((var(--progress) * var(--circumference)) / 100);
}
.circular-progress circle {
cx: var(--half-size);
cy: var(--half-size);
r: var(--radius);
stroke-width: var(--stroke-width);
fill: none;
stroke-linecap: round;
}
.circular-progress circle.bg {
stroke: #ddd;
}
.circular-progress circle.fg {
transform: rotate(-90deg);
transform-origin: var(--half-size) var(--half-size);
stroke-dasharray: var(--dash) calc(var(--circumference) - var(--dash));
transition: stroke-dasharray 0.3s linear 0s;
stroke: #5394fd;
}
这看起来可能很多,但它主要是设置CSS变量并使用它们来计算我们需要的值。我想指出的一个很酷的事情是,pi
常量可以作为calc()
函数的一部分使用!在我开始写这篇文章之前,我不知道这一点,我对此非常兴奋。
此时,如果您使用一些JavaScript来操作--progress
变量的值,您将看到进度条填满。添加的transition
属性将使进度条平滑地动画。
定时进度条
您是否曾经在移动游戏中观看过广告?您知道,如果您观看整个广告,它们会给您奖励吗?它们通常有一个进度条,随着广告的播放而填满。或者当您观看时,它会倒计时,就像一个倒计时器。无论您看到的是哪种风格,它们都是相同概念的变体。
_我们如何创建一个在预定时间内填满的进度条?_我们可以使用JavaScript和Window.requestAnimationFrame()
来实现,但那不太酷。相反,我们可以使用animation
属性来在一定时间内将--progress
变量从0
动画到100
。
@keyframes progress-animation {
from {
--progress: 0;
}
to {
--progress: 100;
}
}
如果您尝试将其与我们的SVG连接起来,您会注意到它的工作方式并不像您想象的那样。这是因为浏览器实际上不知道如何处理--progress
变量。它不知道它是一个数字,所以不知道如何对其进行动画处理。
幸运的是,CSS已经提出了一个解决方案。@property
规则允许我们定义自定义属性并告诉浏览器它们的类型。在这种情况下,我们想告诉浏览器--progress
是一个数字。
@property --progress {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
[!WARNING]
在撰写本文时(2023年12月),
@property
规则的浏览器支持有限。请在使用之前进行检查。
现在浏览器知道如何处理--progress
变量,我们可以将其与动画连接起来。
.circular-progress {
animation: progress-animation 5s linear 0s 1 forwards;
}
这段代码将会使 --progress
变量在 5 秒内从 0
变化到 100
。forwards
关键字告诉浏览器保持动画的最终值。如果没有这个关键字,进度条在动画结束后会重置为 0
。你可以通过将 animation-direction
属性设置为 reverse
,并使用 backwards
替代 forwards
来创建相反的效果。
将所有内容整合在一起
在本文中,我们已经涵盖了很多内容。我们从一个简单的 SVG 元素开始,到一个完全功能的进度条。我们使用了 CSS 变量、数学函数,甚至还使用了一个新的 CSS 特性。让我们来看一下最终的代码。
<svg width="250" height="250" viewBox="0 0 250 250" class="circular-progress">
<circle class="bg"></circle>
<circle class="fg"></circle>
</svg>
.circular-progress {
--size: 250px;
--half-size: calc(var(--size) / 2);
--stroke-width: 20px;
--radius: calc((var(--size) - var(--stroke-width)) / 2);
--circumference: calc(var(--radius) * pi * 2);
--dash: calc((var(--progress) * var(--circumference)) / 100);
animation: progress-animation 5s linear 0s 1 forwards;
}
.circular-progress circle {
cx: var(--half-size);
cy: var(--half-size);
r: var(--radius);
stroke-width: var(--stroke-width);
fill: none;
stroke-linecap: round;
}
.circular-progress circle.bg {
stroke: #ddd;
}
.circular-progress circle.fg {
transform: rotate(-90deg);
transform-origin: var(--half-size) var(--half-size);
stroke-dasharray: var(--dash) calc(var(--circumference) - var(--dash));
transition: stroke-dasharray 0.3s linear 0s;
stroke: #5394fd;
}
@property --progress {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
@keyframes progress-animation { from { --progress: 0; } to { --progress: 100; } } ```
这是代码在CodePen上的演示:
https://codepen.io/chalarangelo/pen/mdodgeL
结论
使用现代的HTML和CSS,我们创建了一个圆形进度条。这个设置可以作为一个很好的起点供您进行实验。您可以直接使用它,或者根据需要进行扩展,如果需要的话,可以加入一点JavaScript。您甚至可以将其转换为Web组件或React组件,以供您的项目使用。