如何使用SVG实现一个环形进度条的效果?

前言

在前端开发中,环形进度条是一种常见的功能效果,在管理后台项目的数据统计功能中用得比较多,一般情况下,我们都会直接使用组件库来实现,比如 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 标签中加入 xy 属性,从左上角开始,向右边偏移 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标签中的x1y1x2y2属性分别代表起点横坐标、起点纵坐标、终点横坐标、终点纵坐标。

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-dasharraystroke-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>

参考文章:juejin.cn/post/699807...

相关推荐
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑2136 小时前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程