SVG制作环形进度条 带动画 — vue3.0版

747 阅读1分钟

本文参考作者:yyzclyang的文章进行的实现,由于原文章的实现不符合我自己的需求,所以在此基础上进行了一个二次创作的vue3.0版本。

最终效果

CircularProgressBar.gif

由两部分组成,底部的灰色和上面的蓝色。

<svg :width="size" :height="size" class="circle-rotate">
    <circle
      :r="radius"
      :cx="cx"
      :cy="cy"
      fill="transparent"
      :stroke="bgColor"
      :stroke-width="strokeWidth"
    />
    <circle
      class="circle"
      :style="`transition: stroke-dashoffset ${animationTime}s linear;font-size: ${fontSize}px`"
      :r="radius"
      :cx="cx"
      :cy="cy"
      fill="transparent"
      :stroke="color"
      :stroke-width="strokeWidth"
      :stroke-dasharray="circumference"
      :stroke-dashoffset="progress"
    >
    </circle>
  </svg>

旋转90度让进度条从顶部开始

.circle-rotate {
  transform: rotate(-90deg);
}

圆弧

circle 实现的是一个完整的圆圈,要实现圆弧效果该怎么做呢?

这里是利用的 stroke-dasharray 来实现的。

stroke-dasharraysvg 中表示描边的是虚线,它可以包含两个值,第一个值是虚线的宽度,第二个是虚线的间距,如果我们把虚线的宽度设置成圆弧的长度,间距设置为超过空白圆弧的长度即可实现圆弧效果。

比如说实现一个 50% 的圆弧,那么第一个值就为 50% * 2 * Math.PI * r,第二个值直接设置为 2 * Math.PI * r,这样不管怎么变,空白圆弧的长度是够的。

线条的圆角 由于线条的圆角是外切会导致进度条多出一块,内切圆角目前也没有实现,所以本文并未使用圆角。

 stroke-linecap="round" 这个属性可以设置线条圆角
 <circle
  :r="radius"
  :cx="cx"
  :cy="cy"
  fill="transparent"
  :stroke="color"
  :stroke-width="strokeWidth"
  stroke-linecap="round"
  :stroke-dasharray="circumference"
  :stroke-dashoffset="progress"
/>

HTML部分

  <template>
      <div class="circle-main">
        <div class="circle-main-box" :style="[{ width: size + 'px', height: size + 'px' }]">
          <svg :width="size" :height="size" class="circle-rotate">
            <circle
              :r="radius"
              :cx="cx"
              :cy="cy"
              fill="transparent"
              :stroke="bgColor"
              :stroke-width="strokeWidth"
            />
            <circle
              class="circle"
              :style="`transition: stroke-dashoffset ${animationTime}s linear;font-size: ${fontSize}px`"
              :r="radius"
              :cx="cx"
              :cy="cy"
              fill="transparent"
              :stroke="color"
              :stroke-width="strokeWidth"
              :stroke-dasharray="circumference"
              :stroke-dashoffset="progress"
            ></circle>
          </svg>
          <span class="count-num" :style="[{ 'font-size': size * 0.3 + 'px' }]"
            >{{ countNum }}%</span
          >
        </div>
      </div>
   </template>

JS+CSS部分

 <script lang="ts" setup>
import { computed } from "vue";
const props = withDefaults(
  defineProps<{
    valueNum: number;
    size: number;
    strokeWidth: number;
    color: string;
    bgColor: string;
    animationTimeMs: number;
    fontSize: number;
  }>(),
  {
    valueNum: 0,
    size: 66,
    strokeWidth: 10,
    color: "#001E70",
    bgColor: "#E8F2FF",
    animationTimeMs: 500,
    fontSize: 18,
  }
);
const { valueNum, size, strokeWidth, color, animationTimeMs } = toRefs(props);
const animationTime = computed(() => {
  return animationTimeMs.value / 1000;
}); // 动画执行时间
const cx = computed(() => {
  return size.value / 2;
}); // 圆心x轴坐标

const cy = computed(() => {
  return size.value / 2;
}); // 圆心y轴坐标

const radius = computed(() => {
  return (size.value - strokeWidth.value) / 2;
}); // 半径

const circumference = computed(() => {
  return 2 * Math.PI * radius.value;
}); // 圆周长

const progress = computed(() => {
  return (1 - nowProgress.value / 100) * circumference.value;
}); // 进度长度

const nowProgress = ref(0);
const countNum = ref(0);
const run = () => {
  let t = animationTimeMs.value / valueNum.value; // 通过动画时间来计算定时器执行的间隔
  let timer = setInterval(() => {
    if (countNum.value >= valueNum.value) {
      clearTimeout(timer); // 清除定时器
    } else {
      countNum.value++; // 自增
    }
  }, t);
  nextTick(() => {
    // 通过nextTick设置进度条的过渡效果
    nowProgress.value = valueNum.value;
  });
};
onBeforeMount(() => {
  run();
});
</script>


<style lang="scss" scoped>
.circle-main-box {
  position: relative;
  display: block;
}
.count-num {
  width: 100px;
  height: 100px;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -50px;
  margin-top: -50px;
  align-items: center;
  justify-content: center;
  display: flex;
  user-select: none;
  font-size: 18px;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #303133;
}
.circle-rotate {
  transform: rotate(-90deg);
}
</style>