vue3 纯SCSS 实现环形进度条
<template>
<view class="flex align-center diygw-col-24 justify-center">
<view
class="progress-circle"
:class="`progress-${innerPercent}`"
>
<view class="inner">
<slot></slot>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
const props = defineProps({
width: {
type: Number || String,
default: '56px'
},
borderWidth: {
type: Number || String,
default: '3px'
},
bgColor: {
type: String,
default: '#fff'
},
notProgressColor: {
type: String,
default: '#ddd'
},
progressColor: {
type: String,
default: '#004898'
},
color: {
type: String,
default: '#333'
},
fontSize: {
type: String,
default: '24rpx'
},
percent: {
type: Number,
default: 0
},
animate: {
type: Boolean,
default: true
},
rate: {
type: Number,
default: 5
}
});
const innerPercent = ref(props.percent);
const complete = computed(() => innerPercent.value == 100);
watch(() => props.percent, (percent) => {
setPercent();
});
onMounted(() => {
setPercent();
});
const setPercent = () => {
if (props.animate) {
stepTo(true);
} else {
innerPercent.value = props.percent;
}
};
const stateTimer = ref(null)
const clearTimeoutFn = () => {
if (stateTimer.value) {
clearTimeout(stateTimer.value);
stateTimer.value = null;
}
};
const stepTo = (topFrame = false) => {
if (topFrame) {
clearTimeoutFn();
}
if (props.percent > innerPercent.value && !complete.value) {
innerPercent.value = innerPercent.value + 1;
}
if (props.percent < innerPercent.value && innerPercent.value > 0) {
innerPercent.value = innerPercent.value - 1;
}
if (innerPercent.value !== props.percent) {
stateTimer.value = setTimeout(() => {
stepTo();
}, props.rate);
}
};
</script>
<style lang="scss" scoped>
.progress-circle {
$diythemeColor: v-bind('props.progressColor');
$diybackColor: v-bind('props.notProgressColor');
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: v-bind('props.width');
height: v-bind('props.width');
border-radius: 50%;
transition: transform 1s;
background-color: $diybackColor;
padding: v-bind('props.borderWidth');
box-sizing: border-box;
.inner {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
z-index: 1;
background-color: v-bind('props.bgColor');
}
&:before {
content: '';
left: 0;
top: 0;
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: $diythemeColor;
}
$step: 1;
$loops: 99;
$increment: 3.6;
$half: 50;
@for $i from 0 through $loops {
&.progress-#{$i * $step}:before {
@if $i < $half {
$nextDeg: 90deg+($increment * $i);
background-image: linear-gradient(90deg, $diybackColor 50%, transparent 50%, transparent), linear-gradient($nextDeg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
}
@else {
$nextDeg: -90deg+($increment * ($i - $half));
background-image: linear-gradient($nextDeg, $diythemeColor 50%, transparent 50%, transparent), linear-gradient(270deg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
}
}
}
.progress-number {
width: 100%;
line-height: 1;
text-align: center;
font-size: v-bind('props.fontSize');
color: v-bind('props.color');
}
}
</style>
vue2 纯SCSS 实现环形进度条
<template>
<view class="flex align-center diygw-col-24 justify-center">
<view class="progress-circle " :class="'progress-' + innerPercent" :style="{
'--not-progress-color': notProgressColor,
'--bg-color': bgColor,
'--color': color,
'--progress-color': progressColor,
'--width': $u.addUnit(width),
'--font-size': $u.addUnit(fontSize),
'--border-width': $u.addUnit(borderWidth)
}">
<view class="inner">
<!-- <view class="progress-number">{{ innerPercent }}%</view> -->
<slot></slot>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
width: {
type: Number,
default: 52
},
borderWidth: {
type: Number,
default: 3
},
bgColor: {
type: String,
default: '#fff'
},
notProgressColor: {
type: String,
default: '#ddd'
},
progressColor: {
type: String,
default: '#004898'
},
color: {
type: String,
default: '#333'
},
fontSize: {
type: Number,
default: 24
},
/**
* 进度(0-100)
*/
percent: {
type: Number,
default: 0
},
/**
* 是否动画
*/
animate: {
type: Boolean,
default: true
},
/**
* 动画速率
*/
rate: {
type: Number,
default: 5
}
},
computed: {
/**
* @private
*/
complete() {
return this.innerPercent == 100
}
},
watch: {
percent(percent) {
this.setPercent()
}
},
data() {
return {
innerPercent: 0,
timeout: null
}
},
mounted() {
this.setPercent()
},
methods: {
setPercent() {
if (this.animate) {
this.stepTo(true)
} else {
this.innerPercent = this.percent
}
},
clearTimeout() {
clearTimeout(this.timeout)
Object.assign(this, {
timeout: null
})
},
stepTo(topFrame = false) {
if (topFrame) {
this.clearTimeout()
}
if (this.percent > this.innerPercent && !this.complete) {
this.innerPercent = this.innerPercent + 1
}
if (this.percent < this.innerPercent && this.innerPercent > 0) {
this.innerPercent--
}
if (this.innerPercent !== this.percent) {
this.timeout = setTimeout(() => {
this.stepTo()
}, this.rate)
}
}
}
}
</script>
<style lang="scss" scoped>
.progress-circle {
--progress-color: #63B8FF;
--not-progress-color: #ddd;
--bg-color: #fff;
--width: 240rpx;
--border-width: 10rpx;
--color: #777;
--font-size: 1.5rem;
$diythemeColor: var(--progress-color);
$diybackColor: var(--not-progress-color);
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: var(--width);
height: var(--width);
border-radius: 50%;
transition: transform 1s;
background-color: $diybackColor;
padding: var(--border-width);
.inner {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
z-index: 1;
background-color: var(--bg-color);
}
&:before {
content: '';
left: 0;
top: 0;
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: $diythemeColor;
}
$step: 1;
$loops: 99;
$increment: 3.6;
$half: 50;
@for $i from 0 through $loops {
&.progress-#{$i * $step}:before {
@if $i < $half {
$nextDeg: 90deg+($increment * $i);
background-image: linear-gradient(90deg, $diybackColor 50%, transparent 50%, transparent), linear-gradient($nextDeg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
}
@else {
$nextDeg: -90deg+($increment * ($i - $half));
background-image: linear-gradient($nextDeg, $diythemeColor 50%, transparent 50%, transparent), linear-gradient(270deg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
}
}
}
.progress-number {
width: 100%;
line-height: 1;
text-align: center;
font-size: var(--font-size);
color: var(--color);
}
}
</style>
使用
<CircleProgress
:percent="item.curSoc"
style="position: relative;"
class="full-box">
<view class="number" style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: max-content; white-space: nowrap;">
<template v-if="item.electricCurrentModel === 1">
<view style="font-size: 28rpx; font-weight: bold;">{{item.curSoc}}%</view>
<view style="font-size: 18rpx;">充电中</view>
</template>
<view v-else>充电中</view>
</view>
</CircleProgress>