目录
- 什么是svg
- 用到svg元素、属性介绍
- circle标签
- text标签
- tspan标签
- stroke-dasharray属性
- stroke-linecap属性
- 其他属性
- circle标签添加stroke-width的效果
- 实现loading
- 实现原理
- 实现步骤
- loding优化
- strokeLinecap等于round时的bug
- 0%时的问题
- 96%时的问题
- 进度条loading应用场景
什么是svg
svg是基于XML,由World Wide Web Consortium (W3C)联盟开发的一种开放标准的矢量图形语言,可让你设计激动人心的、高分辨率的Web图形页面。
可能有人不知道XML是什么,你只需知道他和html一样也是一种页面结构。他们同样都是由W3C开发的,只不过xml用来设计web图形,html用来构建web页面。
loading就属于web图形的一种,所以是可以使用基于XML的svg来实现的。同时因为他和html是同一级别的,所以兼容性不必多说。
用到svg元素、属性介绍
circle标签
circle是svg中的一个基础标签,类似于html的div。不同的是div是长方形,circle是圆形。
他有三个专有属性也是基本参数:cx、cy、r。前两个表示圆心的水平、垂直坐标,r表示圆的半径。
<svg viewBox=" 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="" cy="50" r="50"/>
</svg>
text标签
text元素定义了一个由文字组成的图形,可以理解为专门用来装文字的div。
tspan标签
在text元素中,利用内含的tspan元素,可以调整文本和字体的属性以及当前文本的位置、绝对或相对坐标值。
stroke-dasharray属性
stroke-dasharray属性是实现loading最关键的一个属性。
- 这个属性的作用是为他所在的元素画一条线,这条线的位置相当于是border的概念。
- 与border不同的是,他所传入的值只用来表示线的类型类似于border-style,没有颜色和宽度。
- 它用来表示线型的方法不是采用 solid dashed 等这样的名称来命名,而是通过一段有间隔的数字来分别表示线的长度和间隔长度。
<svg width="" height="200" viewPort="0 0 200 300" version="1.1" xmlns="http://www.w3.org/2000/svg">
<line stroke-dasharray=", 5" x1="10" y1="10" x2="190" y2="10" />
<line stroke-dasharray=", 10" x1="10" y1="30" x2="190" y2="30" />
<line stroke-dasharray=", 5" x1="10" y1="50" x2="190" y2="50" />
<line stroke-dasharray=", 1" x1="10" y1="70" x2="190" y2="70" />
<line stroke-dasharray=", 5" x1="10" y1="90" x2="190" y2="90" />
<line stroke-dasharray=".9" x1="10" y1="110" x2="190" y2="110" />
<line stroke-dasharray=", 10, 5" x1="10" y1="130" x2="190" y2="130" />
<line stroke-dasharray=", 10, 5, 10" x1="10" y1="150" x2="190" y2="150" />
<line stroke-dasharray=", 10, 5, 10, 15" x1="10" y1="170" x2="190" y2="170" />
<line stroke-dasharray=", 5, 1, 5" x1="10" y1="190" x2="190" y2="190" />
<style><![CDATA[
line{
stroke: black;
stroke-width:;
}
]]></style>
</svg>
可以看出第一行线的长度和间隔长度都是5。第二行线长是5间隔长度是10,因此空白是实线的2倍。第三行线长是10间隔是5,因此实心是空白的2倍。后面的以此类推。
stroke-linecap属性
stroke-linecap表示线头的类型。这是svg独有的特性,他有三个常见属性如下:
<svg viewBox=" 0 6 6" xmlns="http://www.w3.org/2000/svg">
<line x="1" y1="1" x2="5" y2="1" stroke="black" stroke-linecap="butt" />
<line x="1" y1="3" x2="5" y2="3" stroke="black" stroke-linecap="round" />
<line x="1" y1="5" x2="5" y2="5" stroke="black" stroke-linecap="square" />
<!--线的真实长度-->
<path d="M,1 h4 M1,3 h4 M1,5 h4" stroke="pink" stroke-width="0.025" />
</svg>
这里需要特别注意的是其中粉色线的长度才是真实线的长度,当stroke-linecap的值为round或square时,视觉上会使线的头尾加长。
其他属性
- stroke-width: 表示线的宽度,类似于border-width
- stroke: 表示线的颜色,类似于border-color
- fill 表示填充色,类似于background-color,不同的是只要是svg的元素就可以使用他填充颜色,包括文字标签。当不设置时默认值为黑色,设置为none时为透明。
circle标签添加stroke-width的效果
<svg width="" height="100">
<circle
cx="" cy="50" r="40"
stroke-width="" stroke="red" fill="green" stroke-dasharray="50 252"
></circle>
</svg>
这里注意默认情况下是从圆的最右边顺时针开始画线,而不是最上边
实现loading
实现原理
通过动态控制进度圆stroke-dasharray属性的第一个参数大小,来表示loading进度。
这么说可能很难理解,看完实现步骤再回来看就很好理解了。
实现步骤
1.在svg标签中创建两个圆,为他们设置相同的圆心(cx、cy)、半径r、边框宽度stroke-width。满足 cx + r + stroke-width = svg的宽。
2.为两个圆分别设置他们的边框颜色stroke,这里需要注意的是因为两个圆圆心、半径相同所以第二个圆会覆盖到第一个圆上面。
我们将覆盖在上面的圆(代码中后写的circle)叫做进度圆,他的颜色设置为进度条的颜色。
将被盖住的圆(代码中第一个写的circle)叫做背景圆,他的颜色设置为圆环的背景色。
此时页面应该显示的是100%进度的loading圆环,如下:
<svg width="" height="100">
<!--背景圆-->
<circle cx="" cy="50" r="40" stroke-width="10" stroke="#eee" fill="none"></circle>
<!--进度圆-->
<circle cx="" cy="50" r="40" stroke-width="10" stroke="red" fill="none"
></circle>
</svg>
3.添加进度文字,需要在circle下方添加一个text文本框,在其中放入两个tspan元素。一个用来更新进度,一个放%。
<svg width="" height="100">
<circle cx="" cy="50" r="40" stroke-width="10" stroke="#eee" fill="none"></circle>
<circle cx="" cy="50" r="40" stroke-width="10" stroke="red" fill="none"></circle>
<text x="" y="56" text-anchor="middle">
<tspan></tspan><tspan>%</tspan>
</text>
</svg>
这里需要注意的是,text标签的y属性设置成50会默认偏上,经过我的测试加上其字体大小的三分之一效果刚刚好。
4.使用计时器模拟加载进度并且动态更新进度圆stroke-dasharray属性的第一个参数大小 。这里采用react来模拟计时器。
// app.js
import {useState, useEffect} from 'react';
import Loading from "./loading";
function App() {
const [percent, setPrecent] = useState();
useEffect(() => {
let timer = setInterval(() => {
setPrecent(percent => {
percent+=;
if(percent ===) {
clearInterval(timer);
}
return percent;
});
},)
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 =,
r =,
strokeWidth =,
stroke = 'red',
strokeBg = '#ccc',
strokeLinecap = 'butt',
percent =,
fontSize = '',
fontColor = '#b778c'
}) {
let perimeter = * Math.PI * r;
let strokeDasharray = `${Math.floor(percent / * perimeter)} ${perimeter}`;
return (
<svg className={styles.svg} width={size} height={size}>
// 背景圆
<circle
cx={size /} cy={size / 2} r={r} strokeWidth={strokeWidth} stroke={strokeBg}
></circle>
// 进度圆
<circle
className={styles.show}
cx={size /} cy={size / 2} r={r} strokeWidth={strokeWidth} stroke={stroke}
strokeDasharray={strokeDasharray} strokeLinecap={strokeLinecap}
></circle>
<text x={size /} y={size / 2 + fontSize / 3} fill={fontColor} fontSize={fontSize} textAnchor="middle">
<tspan>{percent}</tspan><tspan>%</tspan>
</text>
</svg>
);
}
注意样式中要加上逆时针旋转90度,因为在圆上画线时默认从圆的最右边开始顺时针画。而我们的要求是从最上方开始顺时针画,所以要逆时针旋转90deg。
// style.module.scss
@keyframes rotate {% {
transform: rotate(-deg);
}% {
transform: rotate(deg);
}% {
transform: rotate(deg);
}% {
transform: rotate(deg);
}% {
transform: rotate(deg);
}
}
svg {
circle {
fill: none;
}
.show {
// 逆时针旋转deg
transform: rotate(-deg);
transform-origin: center;
transition: alls;
animation: rotates linear infinite;
}
}
此时效果如图,已经达到了我们想要的效果。
loding优化
采用默认的线头(butt)时,边缘很尖给人一种很尖锐的感觉,可以通过给strokeLinecap传入round属性将线头变为圆形,这样就看上去更加流畅了。
strokeLinecap等于round时的bug
上面提到将线头strokeLinecap优化为round,但是优化完会产生两个不容易被发现的bug。产生这两个bug的根本原因就在于前面提到过的:
这里需要特别注意的是其中粉色线的长度才是真实线的长度,当stroke-linecap的值为round或square时,视觉上会使线的头尾加长。
0%时的问题
这个问题非常明显,我进度明明是0,确有进度明显不合理。产生这种问题的原因就是当strokeLinecap不为默认值butt时,首尾会超出来一截。
解决问题的方式也非常简单,那就是当strokeLinecap不为默认值butt 且 进度为0时让strokeLinecap属性等于默认值。
let cap = (strokeLinecap !== 'butt' && percent !==) ? strokeLinecap : 'butt';
// 在进度圆上将strokeLinecap属性传入的值替换为cap
// <circle strokeLinecap={cap} ></circle>
96%时的问题
与上面0%问题类似,由于默认多出来的一截会导致进度还没有到100%,只有96%时就已经连到了一起,给人以100%的效果。
之所以会产生这种问题,是因为0%时明明没有进度却占用了一部分的进度值。这就导致原本100%进度对应100%周长变为了100%进度对应100%周长减去strokeLinecap额外带来的周长。
根据观察strokeLinecap额外带来的周长约等于线的宽度strokeWidth,所以解决方案如下。
let strokeDasharray = "";
if(strokeLinecap === 'butt') {
// 当前是默认状态,采用全周长计算
strokeDasharray=`${Math.floor(percent / * perimeter)} ${perimeter}`;
} else {
// 需要剪掉strokeLinecap多出来的那部分
strokeDasharray=`${Math.floor(percent / * (perimeter - strokeWidth))} ${perimeter}`;
}
进度条loading应用场景
只要带有进度的地方都可以使用它,比如常见的下载进度、xxx完成进度、加载页面进度。也可以动态的加载从0到某个进度,常用于金融中自己的持仓占比等。