利用svg實現(xiàn)帶加載進(jìn)度的loading
什么是svg
svg是基于XML,由World Wide Web Consortium (W3C)聯(lián)盟開發(fā)的一種開放標(biāo)準(zhǔn)的矢量圖形語言,可讓你設(shè)計激動人心的、高分辨率的Web圖形頁面。
可能有人不知道XML是什么,你只需知道他和html一樣也是一種頁面結(jié)構(gòu)。他們同樣都是由W3C開發(fā)的,只不過xml用來設(shè)計web圖形,html用來構(gòu)建web頁面。
loading就屬于web圖形的一種,所以是可以使用基于XML的svg來實現(xiàn)的。同時因為他和html是同一級別的,所以兼容性不必多說。
用到svg元素、屬性介紹
circle標(biāo)簽
circle是svg中的一個基礎(chǔ)標(biāo)簽,類似于html的div。不同的是div是長方形,circle是圓形。
他有三個專有屬性也是基本參數(shù):cx、cy、r。前兩個表示圓心的水平、垂直坐標(biāo),r表示圓的半徑。
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <circle cx="50" cy="50" r="50"/> </svg>

text標(biāo)簽
text元素定義了一個由文字組成的圖形,可以理解為專門用來裝文字的div。
tspan標(biāo)簽
在text元素中,利用內(nèi)含的tspan元素,可以調(diào)整文本和字體的屬性以及當(dāng)前文本的位置、絕對或相對坐標(biāo)值。
stroke-dasharray屬性
stroke-dasharray屬性是實現(xiàn)loading最關(guān)鍵的一個屬性。
- 這個屬性的作用是為他所在的元素畫一條線,這條線的位置相當(dāng)于是border的概念。
- 與border不同的是,他所傳入的值只用來表示線的類型類似于
border-style,沒有顏色和寬度。 - 它用來表示線型的方法不是采用 solid dashed 等這樣的名稱來命名,而是通過一段有間隔的數(shù)字來分別表示線的長度和間隔長度。
<svg width="200" height="200" viewPort="0 0 200 300" version="1.1" xmlns="http://www.w3.org/2000/svg">
<line stroke-dasharray="5, 5" x1="10" y1="10" x2="190" y2="10" />
<line stroke-dasharray="5, 10" x1="10" y1="30" x2="190" y2="30" />
<line stroke-dasharray="10, 5" x1="10" y1="50" x2="190" y2="50" />
<line stroke-dasharray="5, 1" x1="10" y1="70" x2="190" y2="70" />
<line stroke-dasharray="1, 5" x1="10" y1="90" x2="190" y2="90" />
<line stroke-dasharray="0.9" x1="10" y1="110" x2="190" y2="110" />
<line stroke-dasharray="15, 10, 5" x1="10" y1="130" x2="190" y2="130" />
<line stroke-dasharray="15, 10, 5, 10" x1="10" y1="150" x2="190" y2="150" />
<line stroke-dasharray="15, 10, 5, 10, 15" x1="10" y1="170" x2="190" y2="170" />
<line stroke-dasharray="5, 5, 1, 5" x1="10" y1="190" x2="190" y2="190" />
<style><![CDATA[
line{
stroke: black;
stroke-width: 2;
}
]]></style>
</svg>
可以看出第一行線的長度和間隔長度都是5。第二行線長是5間隔長度是10,因此空白是實線的2倍。第三行線長是10間隔是5,因此實心是空白的2倍。后面的以此類推。
stroke-linecap屬性
stroke-linecap表示線頭的類型。這是svg獨有的特性,他有三個常見屬性如下:
<svg viewBox="0 0 6 6" xmlns="http://www.w3.org/2000/svg">
<line x1="1" y1="1" x2="5" y2="1" stroke="black" stroke-linecap="butt" />
<line x1="1" y1="3" x2="5" y2="3" stroke="black" stroke-linecap="round" />
<line x1="1" y1="5" x2="5" y2="5" stroke="black" stroke-linecap="square" />
<!--線的真實長度-->
<path d="M1,1 h4 M1,3 h4 M1,5 h4" stroke="pink" stroke-width="0.025" />
</svg>

這里需要特別注意的是其中粉色線的長度才是真實線的長度,當(dāng)stroke-linecap的值為round或square時,視覺上會使線的頭尾加長。
其他屬性
- stroke-width: 表示線的寬度,類似于
border-width - stroke: 表示線的顏色,類似于
border-color - fill 表示填充色,類似于
background-color,不同的是只要是svg的元素就可以使用他填充顏色,包括文字標(biāo)簽。當(dāng)不設(shè)置時默認(rèn)值為黑色,設(shè)置為none時為透明。
circle標(biāo)簽添加stroke-width的效果
<svg width="100" height="100">
<circle
cx="50" cy="50" r="40"
stroke-width="10" stroke="red" fill="green" stroke-dasharray="50 252"
></circle>
</svg>
這里注意默認(rèn)情況下是從圓的最右邊順時針開始畫線,而不是最上邊
實現(xiàn)loading
實現(xiàn)原理
通過動態(tài)控制進(jìn)度圓stroke-dasharray屬性的第一個參數(shù)大小,來表示loading進(jìn)度。
這么說可能很難理解,看完實現(xiàn)步驟再回來看就很好理解了。
實現(xiàn)步驟
1.在svg標(biāo)簽中創(chuàng)建兩個圓,為他們設(shè)置相同的圓心(cx、cy)、半徑r、邊框?qū)挾萻troke-width。滿足 cx + r + stroke-width = svg的寬。
2.為兩個圓分別設(shè)置他們的邊框顏色stroke,這里需要注意的是因為兩個圓圓心、半徑相同所以第二個圓會覆蓋到第一個圓上面。
我們將覆蓋在上面的圓(代碼中后寫的circle)叫做進(jìn)度圓,他的顏色設(shè)置為進(jìn)度條的顏色。
將被蓋住的圓(代碼中第一個寫的circle)叫做背景圓,他的顏色設(shè)置為圓環(huán)的背景色。
此時頁面應(yīng)該顯示的是100%進(jìn)度的loading圓環(huán),如下:
<svg width="100" height="100">
<!--背景圓-->
<circle cx="50" cy="50" r="40" stroke-width="10" stroke="#eee" fill="none"></circle>
<!--進(jìn)度圓-->
<circle cx="50" cy="50" r="40" stroke-width="10" stroke="red" fill="none"
></circle>
</svg>
3.添加進(jìn)度文字,需要在circle下方添加一個text文本框,在其中放入兩個tspan元素。一個用來更新進(jìn)度,一個放%。
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke-width="10" stroke="#eee" fill="none"></circle>
<circle cx="50" cy="50" r="40" stroke-width="10" stroke="red" fill="none"></circle>
<text x="50" y="56" text-anchor="middle">
<tspan>100</tspan><tspan>%</tspan>
</text>
</svg>
這里需要注意的是,text標(biāo)簽的y屬性設(shè)置成50會默認(rèn)偏上,經(jīng)過我的測試加上其字體大小的三分之一效果剛剛好。
4.使用計時器模擬加載進(jìn)度并且動態(tài)更新進(jìn)度圓stroke-dasharray屬性的第一個參數(shù)大小 。這里采用react來模擬計時器。
// app.js
import {useState, useEffect} from 'react';
import Loading from "./loading";
function App() {
const [percent, setPrecent] = useState(0);
useEffect(() => {
let timer = setInterval(() => {
setPrecent(percent => {
percent+= 5;
if(percent === 100) {
clearInterval(timer);
}
return percent;
});
}, 200)
return () => clearInterval(timer);
}, [])
return (
<div >
<Loading percent={percent} />
</div>
);
}
export default App;
// loading/index.jsx
import styles from './style.module.scss';
export default function Loading({
size = 100,
r = 40,
strokeWidth = 10,
stroke = 'red',
strokeBg = '#ccc',
strokeLinecap = 'butt',
percent = 0,
fontSize = '16',
fontColor = '#6b778c'
}) {
let perimeter = 2 * Math.PI * r;
let strokeDasharray = `${Math.floor(percent / 100 * perimeter)} ${perimeter}`;
return (
<svg className={styles.svg} width={size} height={size}>
// 背景圓
<circle
cx={size / 2} cy={size / 2} r={r} strokeWidth={strokeWidth} stroke={strokeBg}
></circle>
// 進(jìn)度圓
<circle
className={styles.show}
cx={size / 2} cy={size / 2} r={r} strokeWidth={strokeWidth} stroke={stroke}
strokeDasharray={strokeDasharray} strokeLinecap={strokeLinecap}
></circle>
<text x={size / 2} y={size / 2 + fontSize / 3} fill={fontColor} fontSize={fontSize} textAnchor="middle">
<tspan>{percent}</tspan><tspan>%</tspan>
</text>
</svg>
);
}注意樣式中要加上逆時針旋轉(zhuǎn)90度,因為在圓上畫線時默認(rèn)從圓的最右邊開始順時針畫。而我們的要求是從最上方開始順時針畫,所以要逆時針旋轉(zhuǎn)90deg。
// style.module.scss
@keyframes rotate {
0% {
transform: rotate(-90deg);
}
25% {
transform: rotate(0deg);
}
50% {
transform: rotate(90deg);
}
75% {
transform: rotate(180deg);
}
100% {
transform: rotate(270deg);
}
}
svg {
circle {
fill: none;
}
.show {
// 逆時針旋轉(zhuǎn)90deg
transform: rotate(-90deg);
transform-origin: center;
transition: all 1s;
animation: rotate 1s linear infinite;
}
}此時效果如圖,已經(jīng)達(dá)到了我們想要的效果。

loding優(yōu)化
采用默認(rèn)的線頭(butt)時,邊緣很尖給人一種很尖銳的感覺,可以通過給strokeLinecap傳入round屬性將線頭變?yōu)閳A形,這樣就看上去更加流暢了。
strokeLinecap等于round時的bug
上面提到將線頭strokeLinecap優(yōu)化為round,但是優(yōu)化完會產(chǎn)生兩個不容易被發(fā)現(xiàn)的bug。產(chǎn)生這兩個bug的根本原因就在于前面提到過的:
這里需要特別注意的是其中粉色線的長度才是真實線的長度,當(dāng)stroke-linecap的值為round或square時,視覺上會使線的頭尾加長。
0%時的問題

這個問題非常明顯,我進(jìn)度明明是0,確有進(jìn)度明顯不合理。產(chǎn)生這種問題的原因就是當(dāng)strokeLinecap不為默認(rèn)值butt時,首尾會超出來一截。
解決問題的方式也非常簡單,那就是當(dāng)strokeLinecap不為默認(rèn)值butt 且 進(jìn)度為0時讓strokeLinecap屬性等于默認(rèn)值。
let cap = (strokeLinecap !== 'butt' && percent !== 0) ? strokeLinecap : 'butt';
// 在進(jìn)度圓上將strokeLinecap屬性傳入的值替換為cap
// <circle strokeLinecap={cap} ></circle>
96%時的問題
與上面0%問題類似,由于默認(rèn)多出來的一截會導(dǎo)致進(jìn)度還沒有到100%,只有96%時就已經(jīng)連到了一起,給人以100%的效果。


之所以會產(chǎn)生這種問題,是因為0%時明明沒有進(jìn)度卻占用了一部分的進(jìn)度值。這就導(dǎo)致原本100%進(jìn)度對應(yīng)100%周長變?yōu)榱?00%進(jìn)度對應(yīng)100%周長減去strokeLinecap額外帶來的周長。
根據(jù)觀察strokeLinecap額外帶來的周長約等于線的寬度strokeWidth,所以解決方案如下。
let strokeDasharray = "";
if(strokeLinecap === 'butt') {
// 當(dāng)前是默認(rèn)狀態(tài),采用全周長計算
strokeDasharray=`${Math.floor(percent / 100 * perimeter)} ${perimeter}`;
} else {
// 需要剪掉strokeLinecap多出來的那部分
strokeDasharray=`${Math.floor(percent / 100 * (perimeter - strokeWidth))} ${perimeter}`;
}

進(jìn)度條loading應(yīng)用場景
只要帶有進(jìn)度的地方都可以使用它,比如常見的下載進(jìn)度、xxx完成進(jìn)度、加載頁面進(jìn)度。也可以動態(tài)的加載從0到某個進(jìn)度,常用于金融中自己的持倉占比等。
到此這篇關(guān)于利用svg實現(xiàn)帶加載進(jìn)度的loading的文章就介紹到這了,更多相關(guān)svg帶加載進(jìn)度loading內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript監(jiān)聽文本框回車事件并過濾文本框空格的方法
這篇文章主要介紹了JavaScript監(jiān)聽文本框回車事件并過濾文本框空格的方法,涉及javascript操作文本框獲取、清空及刪除空格的技巧,需要的朋友可以參考下2015-04-04
JS forEach跳出循環(huán)2種實現(xiàn)方法
這篇文章主要介紹了JS forEach跳出循環(huán)2種實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06
火狐下table中創(chuàng)建form導(dǎo)致兩個table之間出現(xiàn)空白
js加入form導(dǎo)致兩個table之間出現(xiàn)空白,還有另一種說法在table中創(chuàng)建form表單是不符合DOM標(biāo)準(zhǔn)的,會導(dǎo)致post失效,以及js數(shù)據(jù)傳輸失效2013-09-09
JavaScript 解析數(shù)學(xué)表達(dá)式的過程詳解
這篇文章主要介紹了JavaScript 解析數(shù)學(xué)表達(dá)式的過程詳解,本文以一個的解題思路,來分享如何解決問題,解決的過程,可以作為解決工作中一般問題的通用思路,對js解析表達(dá)式相關(guān)知識感興趣的朋友一起看看吧2022-06-06
基于Flowplayer打造一款免費(fèi)的WEB視頻播放器附源碼
Flowplayer是一款免費(fèi)的WEB視頻播放器。它支持播放flv、swf等流媒體和圖片文件,能夠非常流暢的播放視頻文件,支持自定義配置和擴(kuò)展。下面本篇文章給大家介紹基于Flowplayer打造一款免費(fèi)的WEB視頻播放器,需要的朋友可以參考下2015-09-09

