如何僅使用CSS創(chuàng)建一個(gè)環(huán)形進(jìn)度條?
這一系列課程將包含各種 ES6 輔助函數(shù)。它包括處理原語、數(shù)組和對(duì)象的助手,以及算法、DOM 操作函數(shù)和 Node.js 實(shí)用程序等相關(guān)內(nèi)容。
環(huán)形進(jìn)度條在現(xiàn)今的網(wǎng)站中是一個(gè)相當(dāng)常見的元素。然而,對(duì)很多開發(fā)者來說,它們似乎是一個(gè)相當(dāng)大的挑戰(zhàn)。實(shí)際情況是,理解并掌握基礎(chǔ)并不困難。事實(shí)上,借助一些新的CSS特性,這比以往任何時(shí)候都更容易。
環(huán)形進(jìn)度條的結(jié)構(gòu)
簡(jiǎn)單來說,一個(gè)環(huán)形進(jìn)度條就是兩個(gè)圓疊加在一起。下面的圓為背景,上面的圓為進(jìn)度指示器。關(guān)于我們?nèi)绾翁畛溥M(jìn)度指示器的部分我們稍后再講,但是基本的結(jié)構(gòu)可以通過使用一個(gè)<svg>元素和少量的CSS輕松構(gòu)建出來。
<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就是一個(gè)變換屬性。我們將前景圓旋轉(zhuǎn)90度,并將變換原點(diǎn)設(shè)置為圓的中心。這樣,圓就圍繞其中心旋轉(zhuǎn),進(jìn)度指示器從頂部開始。
數(shù)學(xué)計(jì)算
在我們開始之前,不妨花一點(diǎn)時(shí)間理解代碼背后的數(shù)學(xué)原理。
我們需要確定的兩個(gè)值是進(jìn)度條的大小和描邊的寬度。對(duì)于這個(gè)示例,我們確定了大小為250px,描邊寬度為20px。我們將使用這些值來計(jì)算我們需要的其它值。
大小用來設(shè)置SVG元素的寬度和高度屬性,以及viewBox屬性。將其除以二,我們得到125px,對(duì)應(yīng)于圖片中心的坐標(biāo)。這個(gè)值用來設(shè)置圓的cx和cy屬性。
考慮到描邊寬度,我們可以計(jì)算出圓的半徑。半徑是從圓心到邊緣的距離。在這種情況下,半徑是115px,即圖片的大小減去描邊寬度再除以二。
最后,我們可以計(jì)算出圓的周長(zhǎng)。周長(zhǎng)是圓邊緣的長(zhǎng)度。在這種情況下,周長(zhǎng)是722.5px,即2 * π * 115px.。
變量 | 值 | 公式 |
size |
| N/A (user defined) |
stroke |
| N/A (user defined) |
center |
|
|
radius |
|
|
circumference |
|
|
接下來,這些數(shù)字將開始派上用場(chǎng),但我保證我們幾乎不需要做什么數(shù)學(xué)計(jì)算。
填充進(jìn)度指示器
現(xiàn)在我們已經(jīng)有了基本的結(jié)構(gòu),現(xiàn)在需要來填充進(jìn)度指示器。為此,我們將使用 stroke-dasharray屬性,它需要傳入閃爍和長(zhǎng)度的交替值。
要?jiǎng)?chuàng)建一個(gè)進(jìn)度條,我們希望傳入兩個(gè)值:填充部分的長(zhǎng)度和空白部分的長(zhǎng)度。獲取填充部分我們需要將進(jìn)度百分比乘以圓的周長(zhǎng)。獲取空白部分,我們會(huì)從周長(zhǎng)中減去填充部分。
假設(shè)我們想要填充圓的50%,SVG代碼看起來將會(huì)是這樣:
<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>
使進(jìn)度條動(dòng)態(tài)化
硬編碼stroke-dasharray值并不是很有用。我們希望能夠動(dòng)態(tài)地設(shè)置進(jìn)度百分比。這就是之前的CSS變量和數(shù)學(xué)在這里起作用的地方。
給定一個(gè)--progress變量,我們可以相對(duì)容易地計(jì)算stroke-dasharray。知道我們將需要之前的大部分值,我們也可以將它們?cè)O(shè)置為CSS變量。更好的是,我們想要設(shè)置的大多數(shù)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;
}
這可能看起來很多,但其實(shí)主要就是設(shè)置CSS變量,然后使用它們來計(jì)算我們需要的值。我想指出一個(gè)很酷的事情,那就是pi常數(shù)是calc()函數(shù)的一部分!
此時(shí),如果你使用一些JavaScript去操作--progress變量的值,你會(huì)看到進(jìn)度條填充起來。新增的transition屬性將使進(jìn)度條平滑地動(dòng)畫表現(xiàn)。
定時(shí)進(jìn)度條
你是否曾在手機(jī)游戲里看過廣告?你知道的,那種如果你看完整個(gè)廣告就會(huì)給你獎(jiǎng)勵(lì)的那種。它們通常都有一個(gè)進(jìn)度條,隨著廣告的播放而填充。或者說,當(dāng)你觀看時(shí),它就像倒計(jì)時(shí)定時(shí)器一樣慢慢變空。無論你可能看到的是哪種類型,它們都屬于同一概念的變體。
我們?nèi)绾蝿?chuàng)建一個(gè)在預(yù)定時(shí)間內(nèi)填滿的進(jìn)度條呢?我們可以用JavaScript和Window.requestAnimationFrame()來實(shí)現(xiàn),但那樣就不太酷了。取而代之的是,我們可以使用animation屬性來讓--progress變量在設(shè)定的時(shí)間內(nèi)從0變?yōu)?00。
下面是重構(gòu)后的代碼看起來是什么樣的:
@keyframes progress-animation {
from {
--progress: 0;
}
to {
--progress: 100;
}
}
如果你嘗試將這個(gè)連接到我們的SVG,你會(huì)發(fā)現(xiàn)它并不像你想象的那樣運(yùn)作。這是因?yàn)闉g覽器并不知道如何處理--progress變量。它不知道它是一個(gè)數(shù)字,所以不知道如何對(duì)它進(jìn)行動(dòng)畫處理。
幸運(yùn)的是,CSS為此提供了一個(gè)解決方案。@property規(guī)則允許我們定義自定義屬性,并告訴瀏覽器它們是什么類型。在這種情況下,我們想告訴瀏覽器--progress是一個(gè)數(shù)字。
@property --progress {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
現(xiàn)在瀏覽器知道如何處理--progress變量,我們可以將它連接到動(dòng)畫。
.circular-progress {
animation: progress-animation 5s linear 0s 1 forwards;
}
這將在5秒內(nèi)把--progress變量從0變到100。forwards關(guān)鍵字告訴瀏覽器保持動(dòng)畫的最后值。沒有它的話,動(dòng)畫完成后,進(jìn)度條會(huì)重置為0。你可以通過設(shè)置animation-direction屬性為reverse,并使用backwards而不是forwards來創(chuàng)建相反的效果。
整合所有內(nèi)容
我們?cè)谶@篇文章中涵蓋了很多內(nèi)容。我們從一個(gè)簡(jiǎn)單的SVG元素,發(fā)展到一個(gè)功能完整的進(jìn)度條。我們使用了CSS變量、數(shù)學(xué)函數(shù),甚至一個(gè)新的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;
}
}
以下是一個(gè)展示代碼運(yùn)行效果
圖片
結(jié)論
使用現(xiàn)代HTML和CSS,我們創(chuàng)建了一個(gè)圓形進(jìn)度條。這個(gè)設(shè)置可以作為你實(shí)驗(yàn)的好起點(diǎn)。你可以參照使用,也可以擴(kuò)展它以適應(yīng)你的需要,如果需要的話,你可以加入一點(diǎn)JavaScript。你甚至可以將它轉(zhuǎn)換為Web組件或React組件用于你的項(xiàng)目。