效果
軌道
使用 svg 畫個軌道
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke="#333"></circle>
</svg>
簡單的說,就是使用 circle 畫個圓。需要注意的是,軌道實際是 circle 的 stroke,所以目標 svg 尺寸是 100,則圓的半徑是 40,而 stroke 為 10。
接著,按設計,軌道只需要 3/4 個圓即可:
<!-- 3/4 track before rotate -->
<!-- circumference = radius * 2 * PI = 40 * 2 * Math.PI = 251.3274 -->
<!-- stroke-dasharray left = circumference * percent = 251.3274 * 0.75 = 188.4955 -->
<!-- stroke-dasharray right = circumference * (1 - percent) = 251.3274 * (1 - 0.75) = 62.8318 -->
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" stroke="#333"></circle>
</svg>
為了實現這軌道,這個時候需要用到 stroke-dasharray。
為了更好理解這裡 stroke-dasharray 的作用,先畫一個 line:
<svg viewBox="0 0 300 10" style="display: block;">
<line x1="0" y1="5" x2="300" y2="5" stroke-width="10" stroke="#333" stroke-dasharray="75,25"></line>
</svg>
簡單的說,上面 line 長 300,每畫一段 75 的 stroke,接著留空一段 25,如此重複,正好重複 3 次,剛好鋪滿了 300 的長度。
應用到 circle 也是如此,只是它是繞著圓,逆時針的畫 stroke,類比的舉例:
stroke-dasharray 的是長度,這裡就需要透過計算周長,得出 A 與 E 分別是多長:
周長 = 半徑 * 2 * PI = 40 * 2 * Math.PI = 251.3274
A = 周長 * 3/4 = 251.3274 * 0.75 = 188.4955
E = 周長 * 1/4 = 251.3274 * 0.25 = 62.8318
現在還要使用 transform 旋轉 135 度以滿足需求:
<!-- 3/4 track after rotate 135deg -->
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#333"></circle>
</svg>
進度條
先畫一個純色的進度條:
body {
background: black;
}
.gauge {
position: relative;
display: inline-block;
}
.gauge > svg {
width: 200px;
height: 200px;
}
.gauge > span {
color: #fff;
position: absolute;
top: 50%;
left: 0;
width: 100%;
text-align: center;
transform: translate(0, -50%);
font-size: 2em;
}
<!-- stroke-dasharray left = circumference * 0.75 * percent = 188.4955 * 0.10 = 18.8495 -->
<!-- stroke-dasharray right = circumference * 0.75 * (1 - percent) + circumference * (1 - 0.75) = 188.4955 * (1 - 0.10) + 62.8318 = 232.4778 -->
<div class="gauge">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#333"></circle>
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="18.8495,232.4778" transform="rotate(135, 50, 50)" stroke="#ffff00"></circle>
</svg>
<span>10%</span>
</div>
<!-- stroke-dasharray left = circumference * 0.75 * percent = 188.4955 * 0.50 = 94.2477 -->
<!-- stroke-dasharray right = circumference * 0.75 * (1 - percent) + circumference * (1 - 0.75) = 188.4955 * (1 - 0.50) + 62.8318 = 157.0795 -->
<div class="gauge">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#333"></circle>
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="94.2477,157.0795" transform="rotate(135, 50, 50)" stroke="#ffff00"></circle>
</svg>
<span>50%</span>
</div>
<!-- stroke-dasharray left = circumference * 0.75 * percent = 188.4955 * 1.00 = 94.2477 -->
<!-- stroke-dasharray right = circumference * 0.75 * (1 - percent) + circumference * (1 - 0.75) = 188.4955 * (1 - 1.00) + 62.8318 = 157.0795 -->
<div class="gauge">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#333"></circle>
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#ffff00"></circle>
</svg>
<span>100%</span>
</div>
有個很重要的前提,例如圖中的 10%、50%、100% 的百分比,是基於那 3/4 軌道的,不是整個圓,所以計算 stroke-dasharray 的時候,實際考慮的是 3 個部分:
10%
A = s1 = 周長 * 3/4 * progress = 251.3274 * 0.75 * 0.10 = 18.8495
E = s2 + s3 = 周長 * 3/4 * (1 - progress) + 周長 * 1/4 = 251.3274 * 0.75 * (1 - 0.10) + 251.3274 * 0.25 = 232.4778
50%
A = s1 = 周長 * 3/4 * progress = 251.3274 * 0.75 * 0.50 = 94.2477
E = s2 + s3 = 周長 * 3/4 * (1 - progress) + 周長 * 1/4 = 251.3274 * 0.75 * (1 - 0.50) + 251.3274 * 0.25 = 157.0796
100%
A = s1 = 周長 * 3/4 * progress = 251.3274 * 0.75 * 1.00 = 188.4955
E = s2 + s3 = 周長 * 3/4 * (1 - progress) + 周長 * 1/4 = 251.3274 * 0.75 * (1 - 1.00) + 251.3274 * 0.25 = 62.8318
漸變
漸變由最初的從左到右,跟隨軌道的 rotate,最後變成從右上到左下,也就意味著,此處的漸變並不是跟隨軌道從 0 到 100%,僅實現了類似的感覺的模擬。
<!-- progress bar with gradient -->
<!-- stroke-dasharray left = circumference * 0.75 * percent = 188.4955 * 0.30 = 94.2477 -->
<!-- stroke-dasharray right = circumference * 0.75 * (1 - percent) + circumference * (1 - 0.75) = 188.4955 * (1 - 0.30) + 62.8318 = 157.0795 -->
<div class="gauge">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#333"></circle>
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="56.5486,194.7786" transform="rotate(135, 50, 50)" stroke="url(#gauge-gradient)"></circle>
</svg>
<span>30%</span>
</div>
<!-- stroke-dasharray left = circumference * 0.75 * percent = 188.4955 * 0.80 = 94.2477 -->
<!-- stroke-dasharray right = circumference * 0.75 * (1 - percent) + circumference * (1 - 0.75) = 188.4955 * (1 - 0.80) + 62.8318 = 157.0795 -->
<div class="gauge">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#333"></circle>
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="150.7964,100.5308" transform="rotate(135, 50, 50)" stroke="url(#gauge-gradient)"></circle>
</svg>
<span>80%</span>
</div>
動畫
最後,為了實現“效果”中的動畫,需要 CSS 配合 JS 實現:
<!-- with animation -->
<div class="gauge">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="188.4955,62.8318" transform="rotate(135, 50, 50)" stroke="#333"></circle>
<circle id="circle" cx="50" cy="50" r="40" fill="none" stroke-width="10" stroke-dasharray="0,251.3274" transform="rotate(135, 50, 50)" stroke="url(#gauge-gradient3)"></circle>
</svg>
<span>100%</span>
</div>
#circle {
transition: all 1s linear;
}
(function() {
const radius = 40;
const trackPercent = 0.75
const circumference = 40 * 2 * Math.PI;
const percent = 1.00;
const strokeDasharrayLeft = circumference * trackPercent * percent
const strokeDasharrayRight = circumference * trackPercent * (1 - percent) + circumference * (1 - trackPercent)
const circle = document.querySelector('#circle');
function change() {
const strokeDasharray = circle.getAttribute('stroke-dasharray').split(',')
const left = parseFloat(strokeDasharray[0])
const right = parseFloat(strokeDasharray[1])
if (left === 0) {
circle.setAttribute('stroke-dasharray', `${strokeDasharrayLeft},${strokeDasharrayRight}`)
} else {
circle.setAttribute('stroke-dasharray', `0,251.3274`)
}
}
setTimeout(function() {
setInterval(function() {
change()
}, 1000)
change()
}, 0)
})();
JS 的主要作用就是動態的計算 stroke-dasharray,並配合 CSS 的 transition all 即可實現。
Done!