前言
在前端开发中,环形进度条是一种常见的功能效果,在管理后台项目的数据统计功能中用得比较多,一般情况下,我们都会直接使用组件库来实现,比如 ant-design-vue
提供了强大的进度条组件。
在此之前,我一直认为环形进度条不好实现,所以也就没有尝试过,后来我查阅了一些资料,发现可以使用 SVG
来实现环形进度条,然后我便开始尝试,最终顺利把环形进度条给实现了。
最终实现的效果如下:
前置知识
首先我们需要了解一些关于 SVG
的知识,SVG 是一种 XML 语言,类似 XHTML
,可以用来绘制矢量图形,SVG
可以通过定义必要的线和形状来创建一个图形。
1. SVG的简单使用
js
<svg width="200" height="100">
<rect width="100%" height="100%" fill="green" />
<circle cx="50" cy="50" r="50" fill="yellow"></circle>
</svg>
js
基本流程:
- 绘制一个 svg 根标签
- 绘制一个 rect 标签,它是一个绘制矩形的标签,fill 属性为设置背景颜色
- 绘制一个 circle 标签,它是一个绘制圆形的标签 cx 和 cy 是偏移的属性
(默认是0,即圆心在坐标0,0中绘画),r 是半径大小
渲染规则:svg 里的元素渲染顺序、规则是后来居上,越后面渲染的元素越前
2. SVG 定位
js
<svg width="300" height="200">
<rect x="100" y="120" width="100" height="100" fill="#f06" />
<!--x表示横坐标,y表示纵坐标,width表示宽,height表示高-->
</svg>
在 rect
标签中加入 x
和 y
属性,从左上角开始,向右边偏移 100px
的距离,再向下偏移 120px
的距离。
3. fill stroke 属性
fill
属性设置绘制图形中内部的颜色(默认为black
),如果你不想填充色可以将 fill
值设置为 none
js
<rect x="10" y="10" width="50" height="50"></rect>
<rect x="70" y="10" width="50" height="50" fill="red"></rect>
<rect x="130" y="10" width="50" height="50" fill="rgb(91,158,86)"></rect>
stroke
属性设置绘制图形的线条元素
js
<rect x="10" y="10" width="50" height="50"></rect>
<rect x="70" y="10" width="50" height="50" fill="red" stroke="blue"></rect>
<rect x="130" y="10" width="50" height="50" fill="rgb(91,158,86)" stroke="rgb(165,101,43)"></rect>
4. JavaScript 操作 SVG
js
<svg width="100" height="100">
<rect id="Rect" x="20" y="20" width="60" height="50" fill="green"></rect>
</svg>
<script>
const myRect = document.getElementById('Rect')
myRect.addEventListener('click', (e) => {
myRect.setAttribute('width', 88);
myRect.setAttribute('height', 88);
}, false);
</script>
5. 直线 line
以下代码代表绘制一条直线,line标签中的x1
,y1
,x2
,y2
属性分别代表起点横坐标、起点纵坐标、终点横坐标、终点纵坐标。
js
<line x1="10" y1="100" x2="100" y2="20" stroke="red" stroke-width="3px"></line>
6. stroke-dasharray 属性
该属性用于定义路径轮廓的虚线样式,需要指定一组数字,每两个数字之间用逗号隔开,表示虚线样式中实线部分和空白部分的长度。
js
<svg>
<line x1="0" x2="250" y1="50" y2="50" stroke-width="2" stroke="red" stroke-dasharray="5"></line>
<line x1="0" x2="250" y1="80" y2="80" stroke-width="2" stroke="red" stroke-dasharray="10"></line>
<line x1="0" x2="250" y1="120" y2="120" stroke-width="2" stroke="red" stroke-dasharray="5,10"></line>
</svg>
7. stroke-dashoffset 属性
该属性用于起点的偏移,正数为 x
值向左偏移,负数为 x
值向右偏移,传入一个参数,用于设置偏移值。该属性需要搭配上面的 stroke-dasharray
属性使用,否则无法看出偏移效果。
js
<svg>
<line x1="0" x2="250" y1="50" y2="50" stroke-width="2" stroke="red" stroke-dasharray="5"></line>
<line x1="0" x2="250" y1="80" y2="80" stroke-width="2" stroke="red" stroke-dasharray="10" stroke-dashoffset="-50"></line>
<line x1="0" x2="250" y1="120" y2="120" stroke-width="2" stroke="red" stroke-dasharray="5,10" stroke-dashoffset="50"></line>
</svg>
实现原理
利用 stroke-dasharray
和 stroke-dashoffset
属性,将描边的显示进行一个偏移错位。将 stroke-dasharrary
设定为圆的周长,也就是每段实线距离为圆的一圈。之后再利用 stroke-dashoffset
属性进行线条的偏移来实现进度条效果。
代码实现
1. 绘制基本样式
js
<svg>
<circle width="250" height="250" cx="120" cy="120" r="100" fill="none" stroke-width="20" stroke="#0266ff"></circle>
</svg>
2. 添加动画效果
js
<svg>
<circle cx="120" cy="120" r="100"></circle>
</svg>
js
.circle {
width: 250px;
height: 250px;
fill: none;
stroke-width: 20;
stroke: #0266ff;
stroke-dasharray: 628;
stroke-dashoffset: 628;
transition: all 1s;
stroke-linecap: round; // 让切口的变为圆形状
transform: rotate(-85deg); // 旋转
transform-origin: center;
transform-box: fill-box;
}
@keyframes circle { 100%{ stroke-dashoffset:0; } }
3. 添加进度文本
js
<template>
<div class="circular-progress-bar">
<div class="svg-container">
<svg class="svg">
<circle
ref="circleRef"
class="circle"
r="100"
cx="120"
cy="120"
></circle>
</svg>
</div>
<div class="text-progress">
<span class="text">{{ currentValue }}</span>
<span class="progress">%</span>
</div>
</div>
</template>
js
.circular-progress-bar {
position: relative;
}
.text-progress {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
.text {
font-size: 30px;
}
.progress {
font-size: 20px;
}
}
4. 实现交互效果
这里我采用定时器进行进度的模拟效果:
js
<script setup lang="ts">
import { ref } from "vue";
const currentValue = ref<number>(0);
const i = ref<number>(0);
const progressLen = 628;
const circleRef = ref();
const setPercent = (num: number) => {
if (num > 100) return;
circleRef.value.style["strokeDashoffset"] =
progressLen - (progressLen / 100) * num;
currentValue.value = num;
};
setInterval(() => {
i.value += Math.floor(Math.random() * 5);
if (i.value >= 100) {
i.value = 100;
}
setPercent(i.value);
}, 250);
</script>
完整代码
js
<template>
<div class="circular-progress-bar">
<div class="svg-container">
<svg class="svg">
<circle
ref="circleRef"
class="circle"
r="100"
cx="120"
cy="120"
></circle>
</svg>
</div>
<div class="text-progress">
<span class="text">{{ currentValue }}</span>
<span class="progress">%</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const currentValue = ref<number>(0);
const i = ref<number>(0);
const progressLen = 628;
const circleRef = ref();
const setPercent = (num: number) => {
if (num > 100) return;
circleRef.value.style["strokeDashoffset"] =
progressLen - (progressLen / 100) * num;
currentValue.value = num;
};
setInterval(() => {
i.value += Math.floor(Math.random() * 5);
if (i.value >= 100) {
i.value = 100;
}
setPercent(i.value);
}, 250);
</script>
<style lang="scss" scoped>
.circular-progress-bar {
position: relative;
}
.text-progress {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
.text {
font-size: 30px;
}
.progress {
font-size: 20px;
}
}
.svg-container {
display: flex;
justify-content: center;
margin: 100px auto;
.svg {
position: relative;
width: 250px;
height: 250px;
}
.circle {
width: 250px;
height: 250px;
fill: none;
stroke-width: 20;
stroke: #0266ff;
stroke-dasharray: 628;
stroke-dashoffset: 628;
transition: all 1s;
stroke-linecap: round;
transform: rotate(-85deg);
transform-origin: center;
transform-box: fill-box;
.text {
font-size: 20px;
}
.percent {
font-size: 10px;
}
}
}
</style>