如何僅使用CSS創建一個環形進度條?
這一系列課程將包含各種 ES6 輔助函數。它包括處理原語、數組和對象的助手,以及算法、DOM 操作函數和 Node.js 實用程序等相關內容。
環形進度條在現今的網站中是一個相當常見的元素。然而,對很多開發者來說,它們似乎是一個相當大的挑戰。實際情況是,理解并掌握基礎并不困難。事實上,借助一些新的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就是一個變換屬性。我們將前景圓旋轉90度,并將變換原點設置為圓的中心。這樣,圓就圍繞其中心旋轉,進度指示器從頂部開始。
數學計算
在我們開始之前,不妨花一點時間理解代碼背后的數學原理。
我們需要確定的兩個值是進度條的大小和描邊的寬度。對于這個示例,我們確定了大小為250px,描邊寬度為20px。我們將使用這些值來計算我們需要的其它值。
大小用來設置SVG元素的寬度和高度屬性,以及viewBox屬性。將其除以二,我們得到125px,對應于圖片中心的坐標。這個值用來設置圓的cx和cy屬性。
考慮到描邊寬度,我們可以計算出圓的半徑。半徑是從圓心到邊緣的距離。在這種情況下,半徑是115px,即圖片的大小減去描邊寬度再除以二。
最后,我們可以計算出圓的周長。周長是圓邊緣的長度。在這種情況下,周長是722.5px,即2 * π * 115px.。
變量 | 值 | 公式 |
size |
| N/A (user defined) |
stroke |
| N/A (user defined) |
center |
|
|
radius |
|
|
circumference |
|
|
接下來,這些數字將開始派上用場,但我保證我們幾乎不需要做什么數學計算。
填充進度指示器
現在我們已經有了基本的結構,現在需要來填充進度指示器。為此,我們將使用 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;
}現在瀏覽器知道如何處理--progress變量,我們可以將它連接到動畫。
.circular-progress {
animation: progress-animation 5s linear 0s 1 forwards;
}這將在5秒內把--progress變量從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;
}
}以下是一個展示代碼運行效果
圖片
結論
使用現代HTML和CSS,我們創建了一個圓形進度條。這個設置可以作為你實驗的好起點。你可以參照使用,也可以擴展它以適應你的需要,如果需要的話,你可以加入一點JavaScript。你甚至可以將它轉換為Web組件或React組件用于你的項目。



























