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
viewBox
用于指定svg
的视口从左顶点位置以及视口的宽高
- 指定了viewBox属性之后, 视口内的元素都会被平铺满整个svg容器 (自适应父级盒子大小)
- 指定了viewBox属性之后,
svg
元素默认会充满整个容器
circle
画圆:cx
,xy
指定圆心的x
,y
坐标,r
指定圆的半径stroke
: 指定边框颜色stroke-width
: 用于指定边框的宽度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>