Skip to content
本页目录

SVG 实现环形统计条

画一个圆

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <circle cx="50" cy="50" r="48" stroke="green" stroke-width="2" fill="black" />
      </svg>
    </div>
  </div>
</template>

TIP

  1. viewBox 用于指定svg的视口从左顶点位置以及视口的宽高
  • 指定了viewBox属性之后, 视口内的元素都会被平铺满整个svg容器 (自适应父级盒子大小)
  • 指定了viewBox属性之后, svg 元素默认会充满整个容器
  1. circle画圆: cxxy 指定圆心的x, y坐标, r 指定圆的半径
  2. stroke: 指定边框颜色
  3. stroke-width: 用于指定边框的宽度
  4. fill 用于指定填充色
vue
<template>
  <div class="w-200px h-200px mx-auto">
    <!-- 当前的viewBox设置表示,视口的左上点0,0开始,视口的宽高都为100个单位 -->
    <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
      <!-- 这里cx,cy为50,因为viewBox的宽高为100,所以这里实际是将圆心设置在了视口的正中心 -->
      <circle cx="50" cy="50" r="48" stroke="green" stroke-width="2" fill="black" />
      <!--
        将 svg 元素想象成一个矩形的话,那么圆心距离上下左右边的距离都是50,
        这将 r 设置为48实际表示圆的半径为48,因为stroke-width为2,所以,还有2个单位的边框,
        因此48+2就让这个圆内容和边框占满了svg元素的视口
      -->
    </svg>
  </div>
</template>

画一个只有描边的圆

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <circle cx="50" cy="50" r="48" stroke="white" stroke-width="2" fill="none" />
      </svg>
    </div>
  </div>
</template>

画一个半圆

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <!-- stroke-dasharray: 只有一个参数时
         表示先绘制30单位的非空白边框,再绘制一个30单位的空白边框,
         然后一直这样循环往复,直至绘制完整个圆的边框
      -->
        <circle cx="50" cy="50" r="48" stroke-dasharray="30" stroke="white" stroke-width="2" fill="none" />
      </svg>
    </div>
  </div>
</template>
<script setup lang="ts">
/** 圆半径*/
const r = 48
/** 圆周长*/
const C = 2 * Math.PI * r
/** 圆实线边框的长度. 这里设置为周长的80% */
const stroke = C * 0.7
/** 空白边框的长度。这里表示 周长-实现边框的长度剩下的长度都是空白边框*/
const blankLen = C - stroke
// console.log(C, stroke, blankLen) // 301.59289474462014 211.1150263212341 90.47786842338604
</script>

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">

        <!-- stroke-dasharray 有两个参数时:
        表示先绘制第一个参数单位的非空白边框,
        再绘制第二个参数的空白边框,然后如此循环往复,
        直至绘制完整个圆的边框 -->

        <!-- 因为当前 stroke-dasharray 的第一个参数和第二个参数是根据圆的周长计算出来的,
          因此体现出的效果就是圆的边框分为了两部分,
          一部分是有填充颜色的,一部分是没有填充颜色的,
          也就是半环的效果 -->

        <!-- 因为stroke起始位置为右侧中间,因此从右侧中间开始顺时针方向画有填充颜色的边框,
          再画无填充颜色的边框,直至填充完整个圆的周长 -->
        <circle
          cx="50"
          cy="50"
          r="48"
          :stroke-dasharray="`${stroke} ${blankLen}`"
          stroke="white"
          stroke-width="2"
          fill="none"
        />
      </svg>
    </div>
  </div>
</template>
<script setup lang="ts">
/** 圆半径*/
const r = 48
/** 圆周长*/
const C = 2 * Math.PI * r
/** 圆实线边框的长度. 这里设置为周长的80% */
const stroke = C * 0.7
/** 空白边框的长度。这里表示 周长-实现边框的长度剩下的长度都是空白边框*/
const blankLen = C - stroke
// console.log(C, stroke, blankLen) // 301.59289474462014 211.1150263212341 90.47786842338604
/* 计算stroke的偏移量,使的环的缺口在正下方 */
const offset = -C / 4 - blankLen / 2 + 4
</script>

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <!-- stroke-dashoffset表示stroke的偏移量,负数表示顺时针偏移,正数则逆时针偏移,0表示不偏移 -->
        <!-- stroke-linecap表示边框的风格,这里表示圆角边框,圆角的半径由stroke-width控制 -->
        <circle
          cx="50"
          cy="50"
          r="45"
          :stroke-dasharray="`${stroke} ${blankLen}`"
          stroke="white"
          stroke-width="5"
          fill="none"
          :stroke-dashoffset="offset"
          stroke-linecap="round"
        />
      </svg>
    </div>
  </div>
</template>

加入渐变

<script setup lang="ts">
/** 圆半径*/
const r = 48
/** 圆周长*/
const C = 2 * Math.PI * r
/** 圆实线边框的长度. 这里设置为周长的80% */
const stroke = C * 0.7
/** 空白边框的长度。这里表示 周长-实现边框的长度剩下的长度都是空白边框*/
const blankLen = C - stroke
// console.log(C, stroke, blankLen) // 301.59289474462014 211.1150263212341 90.47786842338604
/* 计算stroke的偏移量,使的环的缺口在正下方 */
const offset = -C / 4 - blankLen / 2 + 4
</script>

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <div class="container">
        <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
          <defs>
            <!-- 设置一个红黄渐变 -->
            <linearGradient id="gradient">
              <stop offset="0%" style="stop-color: red" />
              <stop offset="100%" style="stop-color: yellow" />
            </linearGradient>
          </defs>

          <!-- 应用红黄渐变'url(#gradient)' -->
          <circle
            cx="50"
            cy="50"
            r="45"
            :stroke-dasharray="`${stroke} ${blankLen}`"
            stroke="url(#gradient)"
            stroke-width="5"
            fill="none"
            :stroke-dashoffset="offset"
            stroke-linecap="round"
          />
        </svg>
      </div>
    </div>
  </div>
</template>

双层圆实现进度效果

<script setup lang="ts">
/**
 * 计算圆相关属性
 * @param {number} r 圆半径
 * @param {number} strokePercent  有色边框长度占圆周长的百分比(是个小数0-1表示0%-100%)
 */
function calc(r: number, strokePercent: number, strokeWidth: number) {
  /**  圆周长*/
  const C = 2 * Math.PI * r
  /** 圆实线边框的长度. 这里设置为周长的80% */
  const stroke = C * strokePercent
  /** 空白边框的长度。这里表示 周长-实现边框的长度剩下的长度都是空白边框 */
  const blankLen = C - stroke
  /** 计算stroke的偏移量,使的环的缺口在正下方 */
  const offset = -C / 4 - blankLen / 2 - strokeWidth / 2 / 2 + 1
  return { stroke, blankLen, offset }
}

const r = 40
const strokeWidth = 10
const { stroke, blankLen, offset } = calc(r, 0.65, strokeWidth)

const r2 = 40
const strokeWidth2 = 5
const { stroke: stroke2, blankLen: blankLen2 } = calc(r2, 0.5, strokeWidth2)

const offset2 = offset
</script>

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <defs>
          <linearGradient id="gradient">
            <stop offset="0%" style="stop-color: red" />
            <stop offset="100%" style="stop-color: yellow" />
          </linearGradient>
        </defs>
        <circle
          cx="50"
          cy="50"
          :r="r"
          :stroke-dasharray="`${stroke} ${blankLen}`"
          stroke="rgba(255, 255, 255, 0.4)"
          :stroke-width="strokeWidth"
          fill="none"
          :stroke-dashoffset="offset"
          stroke-linecap="round"
        />
        <circle
          cx="50"
          cy="50"
          :r="r2"
          :stroke-dasharray="`${stroke2} ${blankLen2}`"
          stroke="url(#gradient)"
          :stroke-width="strokeWidth2"
          fill="none"
          :stroke-dashoffset="offset2"
          stroke-linecap="round"
        />
      </svg>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'

/**
 * 计算圆相关属性
 * @param {number} r 圆半径
 * @param {number} strokePercent  有色边框长度占圆周长的百分比(是个小数0-1表示0%-100%)
 */
function calc(r: number, strokePercent: number, strokeWidth: number) {
  /** 圆周长 */
  const C = 2 * Math.PI * r
  /** 圆实线边框的长度. 这里设置为周长的80% */
  const stroke = C * strokePercent
  /** 空白边框的长度。这里表示 周长-实现边框的长度剩下的长度都是空白边框 */
  const blankLen = C - stroke
  /** 计算stroke的偏移量,使的环的缺口在正下方 */
  const offset = -C / 4 - blankLen / 2 - strokeWidth / 2 / 2 + 1
  return { stroke, blankLen, offset }
}

const r = 40
const strokeWidth = 10
const { stroke, blankLen, offset } = calc(r, 0.65, strokeWidth)

const r2 = 40
const strokeWidth2 = 5
const { stroke: stroke2, blankLen: blankLen2 } = calc(r2, 0.5, strokeWidth2)

const offset2 = offset

const strokeRef = ref<number>(stroke2)
const blankLenRef = ref<number>(blankLen2)

/** 计算第二个圆的有色边框长度stroke,无色边框长度blankLen,stroke偏移量offset */
const { stroke: stroke3, blankLen: blankLen3 } = calc(r2, 0.65, strokeWidth2)

/** 修改进度百分比 */
function onChange() {
  /** 产生0-1之间的随机数 */
  const percent = Math.random()
  /** 计算进度百分比实际的有色边框长度 */
  strokeRef.value = stroke3 * percent
  /** 计算剩余的无色边框长度 */
  blankLenRef.value = blankLen3 + (stroke3 - strokeRef.value)
}

const flag = window.setInterval(onChange, 600)

onBeforeUnmount(() => {
  if (window) window.clearInterval(flag)
})
</script>

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <defs>
          <linearGradient id="gradient">
            <stop offset="0%" style="stop-color: red" />
            <stop offset="100%" style="stop-color: yellow" />
          </linearGradient>
        </defs>

        <circle cx="50" cy="50" :r="r" :stroke-dasharray="`${stroke} ${blankLen}`" stroke="rgba(255, 255, 255, 0.4)"
          :stroke-width="strokeWidth" fill="none" :stroke-dashoffset="offset" stroke-linecap="round" />
        <circle cx="50" cy="50" :r="r2" :stroke-dasharray="`${strokeRef} ${blankLenRef}`" stroke="url(#gradient)"
          :stroke-width="strokeWidth2" fill="none" :stroke-dashoffset="offset2" stroke-linecap="round" />
      </svg>
    </div>
  </div>
</template>

<style lang="scss" scoped>
// 实现动效
circle {
  -webkit-transition: stroke-dasharray 0.3s;
  transition: stroke-dasharray 0.3s;
}
</style>
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'

/**
 * 计算圆相关属性
 * @param {number} r 圆半径
 * @param {number} strokePercent  有色边框长度占圆周长的百分比(是个小数0-1表示0%-100%)
 */
function calc(r: number, strokePercent: number, strokeWidth: number) {
  /** 圆周长 */
  const C = 2 * Math.PI * r
  /** 圆实线边框的长度. 这里设置为周长的80% */
  const stroke = C * strokePercent
  /** 空白边框的长度。这里表示 周长-实现边框的长度剩下的长度都是空白边框 */
  const blankLen = C - stroke
  /** 计算stroke的偏移量,使的环的缺口在正下方 */
  const offset = -C / 4 - blankLen / 2 - strokeWidth / 2 / 2 + 1
  return { stroke, blankLen, offset }
}

const r = 40
const strokeWidth = 10
const { stroke, blankLen, offset } = calc(r, 0.65, strokeWidth)

const r2 = 40
const strokeWidth2 = 5
const { stroke: stroke2, blankLen: blankLen2 } = calc(r2, 0.5, strokeWidth2)

const offset2 = offset

const strokeRef = ref<number>(stroke2)
const blankLenRef = ref<number>(blankLen2)

/** 计算第二个圆的有色边框长度stroke,无色边框长度blankLen,stroke偏移量offset */
const { stroke: stroke3, blankLen: blankLen3 } = calc(r2, 0.65, strokeWidth2)

const r3 = 30
const strokeWidth3 = 5

// 计算第三个圆的有色边框长度stroke,无色边框长度blankLen,stroke偏移量offset
const { stroke: stroke4, blankLen: blankLen4, offset: offset4 } = calc(r3, 0.65, strokeWidth3)
const stroke4Ref = ref<number>(stroke4)
const blankLen4Ref = ref<number>(blankLen4)

/** 修改进度百分比 */
function onChange() {
  const percent = Math.random()
  strokeRef.value = stroke3 * percent
  blankLenRef.value = blankLen3 + (stroke3 - strokeRef.value)
}

function onChange2() {
  const percent = Math.random()
  stroke4Ref.value = stroke4 * percent
  blankLen4Ref.value = blankLen4 + (stroke4 - stroke4Ref.value)
}

const flag = window.setInterval(() => {
  onChange()
  onChange2()
}, 600)

onBeforeUnmount(() => window.clearInterval(flag))
</script>

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <defs>
          <!-- 设置一个红黄渐变 -->
          <linearGradient id="gradient">
            <stop offset="0%" style="stop-color: red" />
            <stop offset="100%" style="stop-color: yellow" />
          </linearGradient>
        </defs>
        <circle cx="50" cy="50" :r="r" :stroke-dasharray="`${stroke} ${blankLen}`" stroke="rgba(255, 255, 255, 0.4)"
          :stroke-width="strokeWidth" fill="none" :stroke-dashoffset="offset" stroke-linecap="round" />
        <circle cx="50" cy="50" :r="r2" :stroke-dasharray="`${strokeRef} ${blankLenRef}`" stroke="url(#gradient)"
          :stroke-width="strokeWidth2" fill="none" :stroke-dashoffset="offset2" stroke-linecap="round" />

        <circle cx="50" cy="50" :r="r3" :stroke-dasharray="`${stroke4Ref} ${blankLen4Ref}`" stroke="url(#gradient)"
          :stroke-width="strokeWidth3" fill="none" :stroke-dashoffset="-80" stroke-linecap="round" />
      </svg>
    </div>
  </div>
</template>

<style lang="scss" scoped>
circle {
  -webkit-transition: stroke-dasharray 0.3s;
  transition: stroke-dasharray 0.3s;
}
</style>

加入文字

<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'

/**
 * 计算圆相关属性
 * @param {number} r 圆半径
 * @param {number} strokePercent  有色边框长度占圆周长的百分比(是个小数0-1表示0%-100%)
 */
function calc(r: number, strokePercent: number, strokeWidth: number) {
  /** 圆周长 */
  const C = 2 * Math.PI * r
  /** 圆实线边框的长度. 这里设置为周长的80% */
  const stroke = C * strokePercent
  /** 空白边框的长度。这里表示 周长-实现边框的长度剩下的长度都是空白边框 */
  const blankLen = C - stroke
  /** 计算stroke的偏移量,使的环的缺口在正下方 */
  const offset = -C / 4 - blankLen / 2 - strokeWidth / 2 / 2 + 1
  return { stroke, blankLen, offset }
}

const r = 40
const strokeWidth = 10
const { stroke, blankLen, offset } = calc(r, 0.65, strokeWidth)

const r2 = 40
const strokeWidth2 = 5
const { stroke: stroke2, blankLen: blankLen2 } = calc(r2, 0.5, strokeWidth2)

const offset2 = offset

const strokeRef = ref<number>(stroke2)
const blankLenRef = ref<number>(blankLen2)

/** 计算第二个圆的有色边框长度stroke,无色边框长度blankLen,stroke偏移量offset */
const { stroke: stroke3, blankLen: blankLen3 } = calc(r2, 0.65, strokeWidth2)

const r3 = 30
const strokeWidth3 = 5

// 计算第三个圆的有色边框长度stroke,无色边框长度blankLen,stroke偏移量offset
const { stroke: stroke4, blankLen: blankLen4, offset: offset4 } = calc(r3, 0.65, strokeWidth3)
const stroke4Ref = ref<number>(stroke4)
const blankLen4Ref = ref<number>(blankLen4)

/** 修改进度百分比 */
function onChange() {
  const percent = Math.random()
  strokeRef.value = stroke3 * percent
  blankLenRef.value = blankLen3 + (stroke3 - strokeRef.value)
}

function onChange2() {
  const percent = Math.random()
  stroke4Ref.value = stroke4 * percent
  blankLen4Ref.value = blankLen4 + (stroke4 - stroke4Ref.value)
}

const flag = window.setInterval(() => {
  onChange()
  onChange2()
}, 600)

onBeforeUnmount(() => window.clearInterval(flag))
</script>

<template>
  <div class="linear-gradient-pink w-full h-60 rounded-lg flex_ccc">
    <div class="w-200px h-200px mx-auto">
      <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <defs>
          <linearGradient id="gradient">
            <stop offset="0%" style="stop-color: red" />
            <stop offset="100%" style="stop-color: yellow" />
          </linearGradient>
        </defs>
        <circle cx="50" cy="50" :r="r" :stroke-dasharray="`${stroke} ${blankLen}`" stroke="rgba(255, 255, 255, 0.4)"
          :stroke-width="strokeWidth" fill="none" :stroke-dashoffset="offset" stroke-linecap="round" />
        <circle cx="50" cy="50" :r="r2" :stroke-dasharray="`${strokeRef} ${blankLenRef}`" stroke="url(#gradient)"
          :stroke-width="strokeWidth2" fill="none" :stroke-dashoffset="offset2" stroke-linecap="round" />

        <circle cx="50" cy="50" :r="r3" :stroke-dasharray="`${stroke4Ref} ${blankLen4Ref}`" stroke="url(#gradient)"
          :stroke-width="strokeWidth3" fill="none" :stroke-dashoffset="-80" stroke-linecap="round" />

        <text style="font-size: 12px;" y="60" x="26" fill="white">你好世界!</text>
      </svg>
    </div>
  </div>
</template>

<style lang="scss" scoped>
// 实现动效
circle {
  -webkit-transition: stroke-dasharray 0.3s;
  transition: stroke-dasharray 0.3s;
}
</style>